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 <[email protected]>
---
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;
+
+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 <[email protected]>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");
--
2.41.0
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 <[email protected]>
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 <[email protected]>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");
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/[email protected]/config)
compiler: m68k-linux-gcc (GCC) 12.3.0
reproduce: (https://download.01.org/0day-ci/archive/20230701/[email protected]/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 <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
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
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
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 <[email protected]>
> ---
> 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 <[email protected]>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Backlight control for USB-C Thunderbolt Apple displays");
> --
> 2.41.0