diff mbox series

[1/1] backlight: apple_bl_usb: Add Apple Studio Display support

Message ID 20230701120806.11812-2-julius@zint.sh (mailing list archive)
State Handled Elsewhere
Headers show
Series Backlight driver for the Apple Studio Display | expand

Commit Message

Julius Zint July 1, 2023, 12:08 p.m. UTC
The Apple Studio Display does not have any physical buttons and the only
way to get or set the brightness is by sending USB control transfers to a
HID device exposed by the display.

These control transfers take the form of a HID_(GET|SET)_REPORT request
and the payload looks like this:

    struct brightness_ctrl_message_data {
           u8 unknown_1;
           __le16 brightness;
           u8 unkown_2[4];
    } __packed;

When compiled as a module this driver needs to be part of the early boot
environment, otherwise the generic USB HID driver will claim the device.

Signed-off-by: Julius Zint <julius@zint.sh>
---
 drivers/video/backlight/Kconfig        |   8 +
 drivers/video/backlight/Makefile       |   1 +
 drivers/video/backlight/apple_bl_usb.c | 264 +++++++++++++++++++++++++
 3 files changed, 273 insertions(+)
 create mode 100644 drivers/video/backlight/apple_bl_usb.c

Comments

Rafał Miłecki July 1, 2023, 1:32 p.m. UTC | #1
On 1.07.2023 14:08, Julius Zint wrote:
> The Apple Studio Display does not have any physical buttons and the only
> way to get or set the brightness is by sending USB control transfers to a
> HID device exposed by the display.
> 
> These control transfers take the form of a HID_(GET|SET)_REPORT request
> and the payload looks like this:
> 
>      struct brightness_ctrl_message_data {
>             u8 unknown_1;
>             __le16 brightness;
>             u8 unkown_2[4];
>      } __packed;
> 
> When compiled as a module this driver needs to be part of the early boot
> environment, otherwise the generic USB HID driver will claim the device.
> 
> Signed-off-by: Julius Zint <julius@zint.sh>

Few comments below.


> ---
>   drivers/video/backlight/Kconfig        |   8 +
>   drivers/video/backlight/Makefile       |   1 +
>   drivers/video/backlight/apple_bl_usb.c | 264 +++++++++++++++++++++++++
>   3 files changed, 273 insertions(+)
>   create mode 100644 drivers/video/backlight/apple_bl_usb.c
> 
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 51387b1ef012..9383d402ebed 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -290,6 +290,14 @@ config BACKLIGHT_APPLE
>   	  If you have an Intel-based Apple say Y to enable a driver for its
>   	  backlight.
>   
> +config BACKLIGHT_APPLE_USB
> +	tristate "Apple USB Backlight Driver"
> +	depends on USB
> +	help
> +	  If you have an external display from Apple that is attached via USB
> +	  say Y to enable a driver for its backlight. Currently it supports the
> +	  Apple Studio Display.
> +
>   config BACKLIGHT_QCOM_WLED
>   	tristate "Qualcomm PMIC WLED Driver"
>   	select REGMAP
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index f72e1c3c59e9..c42880655113 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_BACKLIGHT_ADP5520)		+= adp5520_bl.o
>   obj-$(CONFIG_BACKLIGHT_ADP8860)		+= adp8860_bl.o
>   obj-$(CONFIG_BACKLIGHT_ADP8870)		+= adp8870_bl.o
>   obj-$(CONFIG_BACKLIGHT_APPLE)		+= apple_bl.o
> +obj-$(CONFIG_BACKLIGHT_APPLE_USB)	+= apple_bl_usb.o
>   obj-$(CONFIG_BACKLIGHT_AS3711)		+= as3711_bl.o
>   obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.o
>   obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH)	+= cr_bllcd.o
> diff --git a/drivers/video/backlight/apple_bl_usb.c b/drivers/video/backlight/apple_bl_usb.c
> new file mode 100644
> index 000000000000..b746b7822974
> --- /dev/null
> +++ b/drivers/video/backlight/apple_bl_usb.c
> @@ -0,0 +1,264 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/usb.h>
> +#include <linux/backlight.h>
> +#include <asm/byteorder.h>
> +
> +#define APPLE_STUDIO_DISPLAY_VENDOR_ID  0x05ac
> +#define APPLE_STUDIO_DISPLAY_PRODUCT_ID 0x1114
> +
> +#define HID_GET_REPORT 0x01
> +#define HID_SET_REPORT 0x09
> +
> +#define HID_REPORT_TYPE_FEATURE 0x0300
> +
> +struct apple_bl_usb_data {
> +	struct usb_interface *usb_interface;
> +	struct usb_device *usb_dev;
> +};
> +
> +struct brightness_ctrl_message_data {
> +	u8 unknown_1;
> +	__le16 brightness;
> +	u8 unkown_2[4];

Typo


> +} __packed;
> +
> +void init_ctrl_msg_data(struct brightness_ctrl_message_data *msg)
> +{
> +	memset(msg, 0, sizeof(struct brightness_ctrl_message_data));
> +	msg->unknown_1 = 0x01;
> +}
> +
> +void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
> +				 u16 brightness_value)
> +{
> +	msg->brightness = cpu_to_le16(brightness_value + 400);
> +}
> +
> +u16 get_ctrl_message_brightness(struct brightness_ctrl_message_data *msg)
> +{
> +	return le16_to_cpu(msg->brightness) - 400;
> +}
> +
> +int apple_bl_usb_usb_get_brightness(struct usb_interface *interface,
> +				    struct usb_device *usb_dev,
> +				    int *brightness)
> +{
> +	int err;
> +	u16 interface_nr;
> +	int msg_data_size;
> +	struct brightness_ctrl_message_data *msg_data;
> +
> +	msg_data_size = sizeof(struct brightness_ctrl_message_data);
> +	msg_data = kzalloc(msg_data_size, GFP_KERNEL);

Check for NULL as kzalloc() may fail


> +	memset(msg_data, 0x00, msg_data_size);
> +	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
> +
> +	err = usb_control_msg(usb_dev,
> +			      usb_rcvctrlpipe(usb_dev, 0),
> +			      HID_GET_REPORT,
> +			      USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> +			      /* wValue: HID-Report Type and Report ID */
> +			      HID_REPORT_TYPE_FEATURE | 0x01,
> +			      interface_nr /* wIndex */,
> +			      msg_data,
> +			      msg_data_size,
> +			      HZ);
> +	if (err < 0) {
> +		dev_err(&interface->dev,
> +			"get: usb control message err: %d\n",
> +			err);

Shouldn't you kfree() and return err here?


> +	}
> +	*brightness = get_ctrl_message_brightness(msg_data);
> +	kfree(msg_data);
> +	dev_dbg(&interface->dev, "get brightness: %d\n", *brightness);
> +	return 0;
> +}
> +
> +int apple_bl_usb_usb_set_brightness(struct usb_interface *interface,
> +				    struct usb_device *usb_dev,
> +				    int brightness)
> +{
> +	int err;
> +	u16 interface_nr;
> +	struct brightness_ctrl_message_data *msg_data;
> +
> +	msg_data = kzalloc(sizeof(struct brightness_ctrl_message_data), GFP_KERNEL);

Same here


> +	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
> +	init_ctrl_msg_data(msg_data);
> +	set_ctrl_message_brightness(msg_data, brightness);
> +
> +	err = usb_control_msg(usb_dev,
> +			      usb_sndctrlpipe(usb_dev, 0),
> +			      HID_SET_REPORT,
> +			      USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> +			      /* wValue: HID-Report Type and Report ID */
> +			      HID_REPORT_TYPE_FEATURE | 0x01,
> +			      interface_nr /* wIndex */,
> +			      msg_data,
> +			      sizeof(struct brightness_ctrl_message_data),
> +			      HZ);
> +	kfree(msg_data);
> +	if (err < 0) {
> +		dev_err(&interface->dev,
> +			"set: usb control message err: %d\n",
> +			err);
> +		return err;
> +	}
> +	dev_dbg(&interface->dev, "set brightness: %d\n", brightness);
> +	return 0;
> +}
> +
> +int apple_bl_usb_check_fb(struct backlight_device *bd, struct fb_info *info)
> +{
> +	dev_info(&bd->dev, "check fb\n");
> +	return 0;
> +}

Do we need that function / print?


> +
> +int apple_bl_usb_get_brightness(struct backlight_device *bl)
> +{
> +	int ret;
> +	struct apple_bl_usb_data *data;
> +	int hw_brightness;
> +
> +	data = bl_get_data(bl);
> +	ret = apple_bl_usb_usb_get_brightness(data->usb_interface,
> +					      data->usb_dev,
> +					      &hw_brightness);
> +	if (!ret)
> +		ret = hw_brightness;
> +
> +	return ret;
> +}
> +
> +int apple_bl_usb_update_status(struct backlight_device *bl)
> +{
> +	int err;
> +	struct apple_bl_usb_data *data;
> +
> +	data = bl_get_data(bl);
> +	err = apple_bl_usb_usb_set_brightness(data->usb_interface,
> +					      data->usb_dev,
> +					      bl->props.brightness);
> +	return err;
> +}
> +
> +static const struct backlight_ops apple_bl_usb_ops = {
> +	.update_status  = apple_bl_usb_update_status,
> +	.get_brightness = apple_bl_usb_get_brightness,
> +	.check_fb       = apple_bl_usb_check_fb,
> +};
> +
> +static void apple_bl_usb_disconnect(struct usb_interface *interface)
> +{
> +	struct backlight_device *bl;
> +
> +	dev_dbg(&interface->dev, "disconnect\n");
> +
> +	bl = usb_get_intfdata(interface);
> +	usb_set_intfdata(interface, NULL);
> +	backlight_device_unregister(bl);
> +}
> +
> +static int apple_bl_usb_probe(struct usb_interface *interface,
> +			      const struct usb_device_id *id)
> +{
> +	struct backlight_properties props;
> +	struct backlight_device *bl;
> +	struct usb_device *usb_dev;
> +	struct device *dev;
> +	struct apple_bl_usb_data *data;
> +	int brightness_interface_nr;
> +
> +	dev_dbg(&interface->dev, "probe\n");
> +
> +	dev = &interface->dev;
> +	usb_dev = interface_to_usbdev(interface);
> +
> +	switch (usb_dev->config->desc.bConfigurationValue) {
> +	case 1:
> +		brightness_interface_nr = 0x7;
> +		break;
> +	case 2:
> +		brightness_interface_nr = 0x9;
> +		break;
> +	case 3:
> +		brightness_interface_nr = 0xc;
> +		break;
> +	default:
> +		dev_err(dev,
> +			"unexpected configuration value: %d\n",
> +			usb_dev->config->desc.bConfigurationValue);
> +		return -EINVAL;
> +	}
> +
> +	if (interface->cur_altsetting->desc.bInterfaceNumber != brightness_interface_nr)
> +		return -ENODEV;
> +
> +	data = devm_kzalloc(dev,
> +			    sizeof(struct apple_bl_usb_data),
> +			    GFP_KERNEL);

I don't think devm_kzalloc() can return error pointer. Just check for NULL below.


> +	if (IS_ERR(data)) {
> +		dev_err(dev, "failed to allocate memory\n");
> +		return PTR_ERR(bl);
> +	}
> +	data->usb_interface = interface;
> +	data->usb_dev = usb_dev;
> +
> +	// Valid brightness values for the apple studio display range from 400
> +	// to 60000. Since the backlight subsystem´s brightness value starts
> +	// from 0, we use 0 to 59600 and offset it by the minimum value.
> +	memset(&props, 0, sizeof(props));
> +	props.type = BACKLIGHT_RAW;
> +	props.max_brightness = 59600;
> +
> +	bl = backlight_device_register("apple_studio_display",
> +				       dev,
> +				       data,
> +				       &apple_bl_usb_ops,
> +				       &props);
> +	if (IS_ERR(bl)) {
> +		dev_err(dev, "failed to register backlight\n");
> +		return PTR_ERR(bl);
> +	}
> +	usb_set_intfdata(interface, bl);
> +	return 0;
> +}
> +
> +static int apple_bl_usb_suspend(struct usb_interface *interface,
> +				pm_message_t message)
> +{
> +	dev_dbg(&interface->dev, "suspend\n");
> +	return 0;
> +}
> +
> +static int apple_bl_usb_resume(struct usb_interface *interface)
> +{
> +	dev_dbg(&interface->dev, "resume\n");
> +	return 0;
> +}
> +
> +static const struct usb_device_id id_table[] = {
> +	{
> +		.idVendor    = APPLE_STUDIO_DISPLAY_VENDOR_ID,
> +		.idProduct   = APPLE_STUDIO_DISPLAY_PRODUCT_ID,
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(usb, id_table);
> +
> +static struct usb_driver usb_asdbl_driver = {
> +	.name         = "apple_bl_usb",
> +	.probe        = apple_bl_usb_probe,
> +	.disconnect   = apple_bl_usb_disconnect,
> +	.id_table     = id_table,
> +	.suspend      = apple_bl_usb_suspend,
> +	.resume       = apple_bl_usb_resume,
> +	.reset_resume = apple_bl_usb_resume
> +};
> +module_usb_driver(usb_asdbl_driver);
> +
> +MODULE_AUTHOR("Julius Zint <julius@zint.sh>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");
kernel test robot July 1, 2023, 2 p.m. UTC | #2
Hi Julius,

kernel test robot noticed the following build warnings:

[auto build test WARNING on lee-backlight/for-backlight-next]
[also build test WARNING on lee-leds/for-leds-next drm-misc/drm-misc-next drm-tip/drm-tip linus/master v6.4 next-20230630]
[cannot apply to lee-backlight/for-backlight-fixes]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Julius-Zint/backlight-apple_bl_usb-Add-Apple-Studio-Display-support/20230701-202142
base:   https://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight.git for-backlight-next
patch link:    https://lore.kernel.org/r/20230701120806.11812-2-julius%40zint.sh
patch subject: [PATCH 1/1] backlight: apple_bl_usb: Add Apple Studio Display support
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20230701/202307012107.OW4d1gBR-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 12.3.0
reproduce: (https://download.01.org/0day-ci/archive/20230701/202307012107.OW4d1gBR-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202307012107.OW4d1gBR-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/video/backlight/apple_bl_usb.c:27:6: warning: no previous prototype for 'init_ctrl_msg_data' [-Wmissing-prototypes]
      27 | void init_ctrl_msg_data(struct brightness_ctrl_message_data *msg)
         |      ^~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:33:6: warning: no previous prototype for 'set_ctrl_message_brightness' [-Wmissing-prototypes]
      33 | void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
         |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:39:5: warning: no previous prototype for 'get_ctrl_message_brightness' [-Wmissing-prototypes]
      39 | u16 get_ctrl_message_brightness(struct brightness_ctrl_message_data *msg)
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:44:5: warning: no previous prototype for 'apple_bl_usb_usb_get_brightness' [-Wmissing-prototypes]
      44 | int apple_bl_usb_usb_get_brightness(struct usb_interface *interface,
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:79:5: warning: no previous prototype for 'apple_bl_usb_usb_set_brightness' [-Wmissing-prototypes]
      79 | int apple_bl_usb_usb_set_brightness(struct usb_interface *interface,
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:113:5: warning: no previous prototype for 'apple_bl_usb_check_fb' [-Wmissing-prototypes]
     113 | int apple_bl_usb_check_fb(struct backlight_device *bd, struct fb_info *info)
         |     ^~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:119:5: warning: no previous prototype for 'apple_bl_usb_get_brightness' [-Wmissing-prototypes]
     119 | int apple_bl_usb_get_brightness(struct backlight_device *bl)
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/video/backlight/apple_bl_usb.c:135:5: warning: no previous prototype for 'apple_bl_usb_update_status' [-Wmissing-prototypes]
     135 | int apple_bl_usb_update_status(struct backlight_device *bl)
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/init_ctrl_msg_data +27 drivers/video/backlight/apple_bl_usb.c

    26	
  > 27	void init_ctrl_msg_data(struct brightness_ctrl_message_data *msg)
    28	{
    29		memset(msg, 0, sizeof(struct brightness_ctrl_message_data));
    30		msg->unknown_1 = 0x01;
    31	}
    32	
  > 33	void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
    34					 u16 brightness_value)
    35	{
    36		msg->brightness = cpu_to_le16(brightness_value + 400);
    37	}
    38	
  > 39	u16 get_ctrl_message_brightness(struct brightness_ctrl_message_data *msg)
    40	{
    41		return le16_to_cpu(msg->brightness) - 400;
    42	}
    43	
  > 44	int apple_bl_usb_usb_get_brightness(struct usb_interface *interface,
    45					    struct usb_device *usb_dev,
    46					    int *brightness)
    47	{
    48		int err;
    49		u16 interface_nr;
    50		int msg_data_size;
    51		struct brightness_ctrl_message_data *msg_data;
    52	
    53		msg_data_size = sizeof(struct brightness_ctrl_message_data);
    54		msg_data = kzalloc(msg_data_size, GFP_KERNEL);
    55		memset(msg_data, 0x00, msg_data_size);
    56		interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
    57	
    58		err = usb_control_msg(usb_dev,
    59				      usb_rcvctrlpipe(usb_dev, 0),
    60				      HID_GET_REPORT,
    61				      USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
    62				      /* wValue: HID-Report Type and Report ID */
    63				      HID_REPORT_TYPE_FEATURE | 0x01,
    64				      interface_nr /* wIndex */,
    65				      msg_data,
    66				      msg_data_size,
    67				      HZ);
    68		if (err < 0) {
    69			dev_err(&interface->dev,
    70				"get: usb control message err: %d\n",
    71				err);
    72		}
    73		*brightness = get_ctrl_message_brightness(msg_data);
    74		kfree(msg_data);
    75		dev_dbg(&interface->dev, "get brightness: %d\n", *brightness);
    76		return 0;
    77	}
    78	
  > 79	int apple_bl_usb_usb_set_brightness(struct usb_interface *interface,
    80					    struct usb_device *usb_dev,
    81					    int brightness)
    82	{
    83		int err;
    84		u16 interface_nr;
    85		struct brightness_ctrl_message_data *msg_data;
    86	
    87		msg_data = kzalloc(sizeof(struct brightness_ctrl_message_data), GFP_KERNEL);
    88		interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
    89		init_ctrl_msg_data(msg_data);
    90		set_ctrl_message_brightness(msg_data, brightness);
    91	
    92		err = usb_control_msg(usb_dev,
    93				      usb_sndctrlpipe(usb_dev, 0),
    94				      HID_SET_REPORT,
    95				      USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
    96				      /* wValue: HID-Report Type and Report ID */
    97				      HID_REPORT_TYPE_FEATURE | 0x01,
    98				      interface_nr /* wIndex */,
    99				      msg_data,
   100				      sizeof(struct brightness_ctrl_message_data),
   101				      HZ);
   102		kfree(msg_data);
   103		if (err < 0) {
   104			dev_err(&interface->dev,
   105				"set: usb control message err: %d\n",
   106				err);
   107			return err;
   108		}
   109		dev_dbg(&interface->dev, "set brightness: %d\n", brightness);
   110		return 0;
   111	}
   112	
 > 113	int apple_bl_usb_check_fb(struct backlight_device *bd, struct fb_info *info)
   114	{
   115		dev_info(&bd->dev, "check fb\n");
   116		return 0;
   117	}
   118	
 > 119	int apple_bl_usb_get_brightness(struct backlight_device *bl)
   120	{
   121		int ret;
   122		struct apple_bl_usb_data *data;
   123		int hw_brightness;
   124	
   125		data = bl_get_data(bl);
   126		ret = apple_bl_usb_usb_get_brightness(data->usb_interface,
   127						      data->usb_dev,
   128						      &hw_brightness);
   129		if (!ret)
   130			ret = hw_brightness;
   131	
   132		return ret;
   133	}
   134	
 > 135	int apple_bl_usb_update_status(struct backlight_device *bl)
   136	{
   137		int err;
   138		struct apple_bl_usb_data *data;
   139	
   140		data = bl_get_data(bl);
   141		err = apple_bl_usb_usb_set_brightness(data->usb_interface,
   142						      data->usb_dev,
   143						      bl->props.brightness);
   144		return err;
   145	}
   146
Sam Ravnborg July 1, 2023, 10 p.m. UTC | #3
Hi Julius.

Thanks for posting this. I few nits in the following where you as the
author decide what to ignore and what to update.

	Sam


On Sat, Jul 01, 2023 at 02:08:03PM +0200, Julius Zint wrote:
> The Apple Studio Display does not have any physical buttons and the only
> way to get or set the brightness is by sending USB control transfers to a
> HID device exposed by the display.
> 
> These control transfers take the form of a HID_(GET|SET)_REPORT request
> and the payload looks like this:
> 
>     struct brightness_ctrl_message_data {
>            u8 unknown_1;
>            __le16 brightness;
>            u8 unkown_2[4];
>     } __packed;
> 
> When compiled as a module this driver needs to be part of the early boot
> environment, otherwise the generic USB HID driver will claim the device.

I hope someone else can help here, as I have no clue.

> 
> Signed-off-by: Julius Zint <julius@zint.sh>
> ---
>  drivers/video/backlight/Kconfig        |   8 +
>  drivers/video/backlight/Makefile       |   1 +
>  drivers/video/backlight/apple_bl_usb.c | 264 +++++++++++++++++++++++++
>  3 files changed, 273 insertions(+)
>  create mode 100644 drivers/video/backlight/apple_bl_usb.c
> 
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 51387b1ef012..9383d402ebed 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -290,6 +290,14 @@ config BACKLIGHT_APPLE
>  	  If you have an Intel-based Apple say Y to enable a driver for its
>  	  backlight.
>  
> +config BACKLIGHT_APPLE_USB
> +	tristate "Apple USB Backlight Driver"
> +	depends on USB
> +	help
> +	  If you have an external display from Apple that is attached via USB
> +	  say Y to enable a driver for its backlight. Currently it supports the
> +	  Apple Studio Display.
> +
>  config BACKLIGHT_QCOM_WLED
>  	tristate "Qualcomm PMIC WLED Driver"
>  	select REGMAP
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index f72e1c3c59e9..c42880655113 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -23,6 +23,7 @@ obj-$(CONFIG_BACKLIGHT_ADP5520)		+= adp5520_bl.o
>  obj-$(CONFIG_BACKLIGHT_ADP8860)		+= adp8860_bl.o
>  obj-$(CONFIG_BACKLIGHT_ADP8870)		+= adp8870_bl.o
>  obj-$(CONFIG_BACKLIGHT_APPLE)		+= apple_bl.o
> +obj-$(CONFIG_BACKLIGHT_APPLE_USB)	+= apple_bl_usb.o
>  obj-$(CONFIG_BACKLIGHT_AS3711)		+= as3711_bl.o
>  obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.o
>  obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH)	+= cr_bllcd.o
> diff --git a/drivers/video/backlight/apple_bl_usb.c b/drivers/video/backlight/apple_bl_usb.c
> new file mode 100644
> index 000000000000..b746b7822974
> --- /dev/null
> +++ b/drivers/video/backlight/apple_bl_usb.c
> @@ -0,0 +1,264 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/usb.h>
> +#include <linux/backlight.h>
> +#include <asm/byteorder.h>
> +
> +#define APPLE_STUDIO_DISPLAY_VENDOR_ID  0x05ac
> +#define APPLE_STUDIO_DISPLAY_PRODUCT_ID 0x1114
> +
> +#define HID_GET_REPORT 0x01
> +#define HID_SET_REPORT 0x09
> +
> +#define HID_REPORT_TYPE_FEATURE 0x0300
> +
> +struct apple_bl_usb_data {
> +	struct usb_interface *usb_interface;
> +	struct usb_device *usb_dev;
> +};
> +
> +struct brightness_ctrl_message_data {
> +	u8 unknown_1;
> +	__le16 brightness;
> +	u8 unkown_2[4];
> +} __packed;

A different way to set the brightness value could be:
struct brightness_ctrl_message_data {
	u8 cmd;
	u8[2] brightness;
	u8 unknown[4];
} __packed;

static void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
					u16 brightness_value)
{
	u16 brightness = brightness_value + 400;
	msg->brightness[0] = brightness & 0xff;
	msg->brightness[2] = brightness >> 8;
}

This is similar to what is done in drm_mipi_dsi.
Other backlight drivers (except one) uses similar tricks to handle when
the brightness is more than one byte.

The magic number 400 would be better represented by a constant like:
#define BACKLIGHT_INTENSITY_OFFSET	400
Or something like this.

It also from the code looks like unknown_1 is a command byte.
#define GET_BACKLIGHT_INTENSITY 0x0
#define SET_BACKLIGHT_INTENSITY	0x1

Looks more descriptive than the current hard coding, implicit via memset
or explicit.

> +void init_ctrl_msg_data(struct brightness_ctrl_message_data *msg)
> +{
> +	memset(msg, 0, sizeof(struct brightness_ctrl_message_data));
> +	msg->unknown_1 = 0x01;
> +}
As the build bot already told you, please use static everywhere
possible.
In this case just drop the helper as it has only one user.

> +
> +void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
> +				 u16 brightness_value)
> +{
> +	msg->brightness = cpu_to_le16(brightness_value + 400);
> +}
> +
> +u16 get_ctrl_message_brightness(struct brightness_ctrl_message_data *msg)
> +{
> +	return le16_to_cpu(msg->brightness) - 400;
> +}
> +
> +int apple_bl_usb_usb_get_brightness(struct usb_interface *interface,
> +				    struct usb_device *usb_dev,
> +				    int *brightness)
> +{
> +	int err;
> +	u16 interface_nr;
> +	int msg_data_size;
> +	struct brightness_ctrl_message_data *msg_data;
> +
> +	msg_data_size = sizeof(struct brightness_ctrl_message_data);
> +	msg_data = kzalloc(msg_data_size, GFP_KERNEL);
The struct is so small that you can safely have it as a local variable.
The pointer is almost the same size. And then there is no need to check
for failing allocations.

> +	memset(msg_data, 0x00, msg_data_size);
> +	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
> +
> +	err = usb_control_msg(usb_dev,
> +			      usb_rcvctrlpipe(usb_dev, 0),
> +			      HID_GET_REPORT,
> +			      USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> +			      /* wValue: HID-Report Type and Report ID */
> +			      HID_REPORT_TYPE_FEATURE | 0x01,
> +			      interface_nr /* wIndex */,
> +			      msg_data,
> +			      msg_data_size,
> +			      HZ);
Consider specifying the number of msec to wait, rather than the platform
dependent HZ value that may or may not work.
I found:
#define USB_CTRL_GET_TIMEOUT	5000
#define USB_CTRL_SET_TIMEOUT	5000

They look like the right choices here.

> +	if (err < 0) {
> +		dev_err(&interface->dev,
> +			"get: usb control message err: %d\n",
> +			err);
> +	}
> +	*brightness = get_ctrl_message_brightness(msg_data);
Should this check the first byte that I assume is a command byte?

> +	kfree(msg_data);
> +	dev_dbg(&interface->dev, "get brightness: %d\n", *brightness);
> +	return 0;
> +}
> +
> +int apple_bl_usb_usb_set_brightness(struct usb_interface *interface,
> +				    struct usb_device *usb_dev,
> +				    int brightness)
> +{
> +	int err;
> +	u16 interface_nr;
> +	struct brightness_ctrl_message_data *msg_data;
> +
> +	msg_data = kzalloc(sizeof(struct brightness_ctrl_message_data), GFP_KERNEL);
As above, declare it on the stack.

> +	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
> +	init_ctrl_msg_data(msg_data);
> +	set_ctrl_message_brightness(msg_data, brightness);
> +
> +	err = usb_control_msg(usb_dev,
> +			      usb_sndctrlpipe(usb_dev, 0),
> +			      HID_SET_REPORT,
> +			      USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> +			      /* wValue: HID-Report Type and Report ID */
> +			      HID_REPORT_TYPE_FEATURE | 0x01,
> +			      interface_nr /* wIndex */,
> +			      msg_data,
> +			      sizeof(struct brightness_ctrl_message_data),
> +			      HZ);
> +	kfree(msg_data);
> +	if (err < 0) {
> +		dev_err(&interface->dev,
> +			"set: usb control message err: %d\n",
> +			err);
> +		return err;
> +	}
> +	dev_dbg(&interface->dev, "set brightness: %d\n", brightness);
> +	return 0;
> +}
> +
> +int apple_bl_usb_check_fb(struct backlight_device *bd, struct fb_info *info)
> +{
> +	dev_info(&bd->dev, "check fb\n");
> +	return 0;
> +}
> +
> +int apple_bl_usb_get_brightness(struct backlight_device *bl)
> +{
> +	int ret;
> +	struct apple_bl_usb_data *data;
> +	int hw_brightness;
> +
> +	data = bl_get_data(bl);
> +	ret = apple_bl_usb_usb_get_brightness(data->usb_interface,
> +					      data->usb_dev,
> +					      &hw_brightness);
> +	if (!ret)
> +		ret = hw_brightness;
> +
> +	return ret;
> +}
> +
> +int apple_bl_usb_update_status(struct backlight_device *bl)
> +{
> +	int err;
> +	struct apple_bl_usb_data *data;
> +
> +	data = bl_get_data(bl);
> +	err = apple_bl_usb_usb_set_brightness(data->usb_interface,
> +					      data->usb_dev,
> +					      bl->props.brightness);
Here you should replace bl->props.brightness with
backlight_get_brightness(bl).
This will give you the value 0 when the backlight device is power down
or blank which is often the right thing.

> +	return err;
> +}
> +
> +static const struct backlight_ops apple_bl_usb_ops = {
> +	.update_status  = apple_bl_usb_update_status,
> +	.get_brightness = apple_bl_usb_get_brightness,
> +	.check_fb       = apple_bl_usb_check_fb,
> +};
> +
> +static void apple_bl_usb_disconnect(struct usb_interface *interface)
> +{
> +	struct backlight_device *bl;
> +
> +	dev_dbg(&interface->dev, "disconnect\n");
> +
> +	bl = usb_get_intfdata(interface);
> +	usb_set_intfdata(interface, NULL);
> +	backlight_device_unregister(bl);
> +}
> +
> +static int apple_bl_usb_probe(struct usb_interface *interface,
> +			      const struct usb_device_id *id)
> +{
> +	struct backlight_properties props;
> +	struct backlight_device *bl;
> +	struct usb_device *usb_dev;
> +	struct device *dev;
> +	struct apple_bl_usb_data *data;
> +	int brightness_interface_nr;
> +
> +	dev_dbg(&interface->dev, "probe\n");
> +
> +	dev = &interface->dev;
> +	usb_dev = interface_to_usbdev(interface);
> +
> +	switch (usb_dev->config->desc.bConfigurationValue) {
> +	case 1:
> +		brightness_interface_nr = 0x7;
> +		break;
> +	case 2:
> +		brightness_interface_nr = 0x9;
> +		break;
> +	case 3:
> +		brightness_interface_nr = 0xc;
> +		break;
> +	default:
> +		dev_err(dev,
> +			"unexpected configuration value: %d\n",
> +			usb_dev->config->desc.bConfigurationValue);
> +		return -EINVAL;
Use dev_err_probe() here.
> +	}
> +
> +	if (interface->cur_altsetting->desc.bInterfaceNumber != brightness_interface_nr)
> +		return -ENODEV;
Same here, so you also log something.
> +
> +	data = devm_kzalloc(dev,
> +			    sizeof(struct apple_bl_usb_data),
> +			    GFP_KERNEL);
> +	if (IS_ERR(data)) {
> +		dev_err(dev, "failed to allocate memory\n");
> +		return PTR_ERR(bl);
Same here.
> +	}
> +	data->usb_interface = interface;
> +	data->usb_dev = usb_dev;
> +
> +	// Valid brightness values for the apple studio display range from 400
> +	// to 60000. Since the backlight subsystem´s brightness value starts
> +	// from 0, we use 0 to 59600 and offset it by the minimum value.
> +	memset(&props, 0, sizeof(props));
> +	props.type = BACKLIGHT_RAW;
> +	props.max_brightness = 59600;
> +
> +	bl = backlight_device_register("apple_studio_display",
> +				       dev,
> +				       data,
> +				       &apple_bl_usb_ops,
> +				       &props);
Any particular reason NOT to use devm_backlight_device_register()?

> +	if (IS_ERR(bl)) {
> +		dev_err(dev, "failed to register backlight\n");
> +		return PTR_ERR(bl);
> +	}
> +	usb_set_intfdata(interface, bl);
> +	return 0;
> +}
> +
> +static int apple_bl_usb_suspend(struct usb_interface *interface,
> +				pm_message_t message)
> +{
> +	dev_dbg(&interface->dev, "suspend\n");
> +	return 0;
> +}
> +
> +static int apple_bl_usb_resume(struct usb_interface *interface)
> +{
> +	dev_dbg(&interface->dev, "resume\n");
> +	return 0;
> +}
> +
> +static const struct usb_device_id id_table[] = {
> +	{
> +		.idVendor    = APPLE_STUDIO_DISPLAY_VENDOR_ID,
> +		.idProduct   = APPLE_STUDIO_DISPLAY_PRODUCT_ID,
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(usb, id_table);
> +
> +static struct usb_driver usb_asdbl_driver = {
> +	.name         = "apple_bl_usb",
> +	.probe        = apple_bl_usb_probe,
> +	.disconnect   = apple_bl_usb_disconnect,
> +	.id_table     = id_table,
> +	.suspend      = apple_bl_usb_suspend,
> +	.resume       = apple_bl_usb_resume,
> +	.reset_resume = apple_bl_usb_resume
> +};
> +module_usb_driver(usb_asdbl_driver);
> +
> +MODULE_AUTHOR("Julius Zint <julius@zint.sh>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");
> -- 
> 2.41.0
diff mbox series

Patch

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 51387b1ef012..9383d402ebed 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -290,6 +290,14 @@  config BACKLIGHT_APPLE
 	  If you have an Intel-based Apple say Y to enable a driver for its
 	  backlight.
 
+config BACKLIGHT_APPLE_USB
+	tristate "Apple USB Backlight Driver"
+	depends on USB
+	help
+	  If you have an external display from Apple that is attached via USB
+	  say Y to enable a driver for its backlight. Currently it supports the
+	  Apple Studio Display.
+
 config BACKLIGHT_QCOM_WLED
 	tristate "Qualcomm PMIC WLED Driver"
 	select REGMAP
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index f72e1c3c59e9..c42880655113 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -23,6 +23,7 @@  obj-$(CONFIG_BACKLIGHT_ADP5520)		+= adp5520_bl.o
 obj-$(CONFIG_BACKLIGHT_ADP8860)		+= adp8860_bl.o
 obj-$(CONFIG_BACKLIGHT_ADP8870)		+= adp8870_bl.o
 obj-$(CONFIG_BACKLIGHT_APPLE)		+= apple_bl.o
+obj-$(CONFIG_BACKLIGHT_APPLE_USB)	+= apple_bl_usb.o
 obj-$(CONFIG_BACKLIGHT_AS3711)		+= as3711_bl.o
 obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.o
 obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH)	+= cr_bllcd.o
diff --git a/drivers/video/backlight/apple_bl_usb.c b/drivers/video/backlight/apple_bl_usb.c
new file mode 100644
index 000000000000..b746b7822974
--- /dev/null
+++ b/drivers/video/backlight/apple_bl_usb.c
@@ -0,0 +1,264 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/backlight.h>
+#include <asm/byteorder.h>
+
+#define APPLE_STUDIO_DISPLAY_VENDOR_ID  0x05ac
+#define APPLE_STUDIO_DISPLAY_PRODUCT_ID 0x1114
+
+#define HID_GET_REPORT 0x01
+#define HID_SET_REPORT 0x09
+
+#define HID_REPORT_TYPE_FEATURE 0x0300
+
+struct apple_bl_usb_data {
+	struct usb_interface *usb_interface;
+	struct usb_device *usb_dev;
+};
+
+struct brightness_ctrl_message_data {
+	u8 unknown_1;
+	__le16 brightness;
+	u8 unkown_2[4];
+} __packed;
+
+void init_ctrl_msg_data(struct brightness_ctrl_message_data *msg)
+{
+	memset(msg, 0, sizeof(struct brightness_ctrl_message_data));
+	msg->unknown_1 = 0x01;
+}
+
+void set_ctrl_message_brightness(struct brightness_ctrl_message_data *msg,
+				 u16 brightness_value)
+{
+	msg->brightness = cpu_to_le16(brightness_value + 400);
+}
+
+u16 get_ctrl_message_brightness(struct brightness_ctrl_message_data *msg)
+{
+	return le16_to_cpu(msg->brightness) - 400;
+}
+
+int apple_bl_usb_usb_get_brightness(struct usb_interface *interface,
+				    struct usb_device *usb_dev,
+				    int *brightness)
+{
+	int err;
+	u16 interface_nr;
+	int msg_data_size;
+	struct brightness_ctrl_message_data *msg_data;
+
+	msg_data_size = sizeof(struct brightness_ctrl_message_data);
+	msg_data = kzalloc(msg_data_size, GFP_KERNEL);
+	memset(msg_data, 0x00, msg_data_size);
+	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
+
+	err = usb_control_msg(usb_dev,
+			      usb_rcvctrlpipe(usb_dev, 0),
+			      HID_GET_REPORT,
+			      USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+			      /* wValue: HID-Report Type and Report ID */
+			      HID_REPORT_TYPE_FEATURE | 0x01,
+			      interface_nr /* wIndex */,
+			      msg_data,
+			      msg_data_size,
+			      HZ);
+	if (err < 0) {
+		dev_err(&interface->dev,
+			"get: usb control message err: %d\n",
+			err);
+	}
+	*brightness = get_ctrl_message_brightness(msg_data);
+	kfree(msg_data);
+	dev_dbg(&interface->dev, "get brightness: %d\n", *brightness);
+	return 0;
+}
+
+int apple_bl_usb_usb_set_brightness(struct usb_interface *interface,
+				    struct usb_device *usb_dev,
+				    int brightness)
+{
+	int err;
+	u16 interface_nr;
+	struct brightness_ctrl_message_data *msg_data;
+
+	msg_data = kzalloc(sizeof(struct brightness_ctrl_message_data), GFP_KERNEL);
+	interface_nr = interface->cur_altsetting->desc.bInterfaceNumber;
+	init_ctrl_msg_data(msg_data);
+	set_ctrl_message_brightness(msg_data, brightness);
+
+	err = usb_control_msg(usb_dev,
+			      usb_sndctrlpipe(usb_dev, 0),
+			      HID_SET_REPORT,
+			      USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+			      /* wValue: HID-Report Type and Report ID */
+			      HID_REPORT_TYPE_FEATURE | 0x01,
+			      interface_nr /* wIndex */,
+			      msg_data,
+			      sizeof(struct brightness_ctrl_message_data),
+			      HZ);
+	kfree(msg_data);
+	if (err < 0) {
+		dev_err(&interface->dev,
+			"set: usb control message err: %d\n",
+			err);
+		return err;
+	}
+	dev_dbg(&interface->dev, "set brightness: %d\n", brightness);
+	return 0;
+}
+
+int apple_bl_usb_check_fb(struct backlight_device *bd, struct fb_info *info)
+{
+	dev_info(&bd->dev, "check fb\n");
+	return 0;
+}
+
+int apple_bl_usb_get_brightness(struct backlight_device *bl)
+{
+	int ret;
+	struct apple_bl_usb_data *data;
+	int hw_brightness;
+
+	data = bl_get_data(bl);
+	ret = apple_bl_usb_usb_get_brightness(data->usb_interface,
+					      data->usb_dev,
+					      &hw_brightness);
+	if (!ret)
+		ret = hw_brightness;
+
+	return ret;
+}
+
+int apple_bl_usb_update_status(struct backlight_device *bl)
+{
+	int err;
+	struct apple_bl_usb_data *data;
+
+	data = bl_get_data(bl);
+	err = apple_bl_usb_usb_set_brightness(data->usb_interface,
+					      data->usb_dev,
+					      bl->props.brightness);
+	return err;
+}
+
+static const struct backlight_ops apple_bl_usb_ops = {
+	.update_status  = apple_bl_usb_update_status,
+	.get_brightness = apple_bl_usb_get_brightness,
+	.check_fb       = apple_bl_usb_check_fb,
+};
+
+static void apple_bl_usb_disconnect(struct usb_interface *interface)
+{
+	struct backlight_device *bl;
+
+	dev_dbg(&interface->dev, "disconnect\n");
+
+	bl = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+	backlight_device_unregister(bl);
+}
+
+static int apple_bl_usb_probe(struct usb_interface *interface,
+			      const struct usb_device_id *id)
+{
+	struct backlight_properties props;
+	struct backlight_device *bl;
+	struct usb_device *usb_dev;
+	struct device *dev;
+	struct apple_bl_usb_data *data;
+	int brightness_interface_nr;
+
+	dev_dbg(&interface->dev, "probe\n");
+
+	dev = &interface->dev;
+	usb_dev = interface_to_usbdev(interface);
+
+	switch (usb_dev->config->desc.bConfigurationValue) {
+	case 1:
+		brightness_interface_nr = 0x7;
+		break;
+	case 2:
+		brightness_interface_nr = 0x9;
+		break;
+	case 3:
+		brightness_interface_nr = 0xc;
+		break;
+	default:
+		dev_err(dev,
+			"unexpected configuration value: %d\n",
+			usb_dev->config->desc.bConfigurationValue);
+		return -EINVAL;
+	}
+
+	if (interface->cur_altsetting->desc.bInterfaceNumber != brightness_interface_nr)
+		return -ENODEV;
+
+	data = devm_kzalloc(dev,
+			    sizeof(struct apple_bl_usb_data),
+			    GFP_KERNEL);
+	if (IS_ERR(data)) {
+		dev_err(dev, "failed to allocate memory\n");
+		return PTR_ERR(bl);
+	}
+	data->usb_interface = interface;
+	data->usb_dev = usb_dev;
+
+	// Valid brightness values for the apple studio display range from 400
+	// to 60000. Since the backlight subsystem´s brightness value starts
+	// from 0, we use 0 to 59600 and offset it by the minimum value.
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = 59600;
+
+	bl = backlight_device_register("apple_studio_display",
+				       dev,
+				       data,
+				       &apple_bl_usb_ops,
+				       &props);
+	if (IS_ERR(bl)) {
+		dev_err(dev, "failed to register backlight\n");
+		return PTR_ERR(bl);
+	}
+	usb_set_intfdata(interface, bl);
+	return 0;
+}
+
+static int apple_bl_usb_suspend(struct usb_interface *interface,
+				pm_message_t message)
+{
+	dev_dbg(&interface->dev, "suspend\n");
+	return 0;
+}
+
+static int apple_bl_usb_resume(struct usb_interface *interface)
+{
+	dev_dbg(&interface->dev, "resume\n");
+	return 0;
+}
+
+static const struct usb_device_id id_table[] = {
+	{
+		.idVendor    = APPLE_STUDIO_DISPLAY_VENDOR_ID,
+		.idProduct   = APPLE_STUDIO_DISPLAY_PRODUCT_ID,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver usb_asdbl_driver = {
+	.name         = "apple_bl_usb",
+	.probe        = apple_bl_usb_probe,
+	.disconnect   = apple_bl_usb_disconnect,
+	.id_table     = id_table,
+	.suspend      = apple_bl_usb_suspend,
+	.resume       = apple_bl_usb_resume,
+	.reset_resume = apple_bl_usb_resume
+};
+module_usb_driver(usb_asdbl_driver);
+
+MODULE_AUTHOR("Julius Zint <julius@zint.sh>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");