From c43900349752721feb83d002dd34c66bf99f94f8 Mon Sep 17 00:00:00 2001 From: Roland Reinl Date: Tue, 26 Dec 2023 08:31:15 +0100 Subject: [PATCH] filogic: Add LED driver for GCA230718 Add basic support for the LED driver for GCA230718. - I didn't find any documentation or driver for it, so the information below is purely based on my investigations - If there is already I driver for it, please tell me. Maybe I didn't search enough - I implemented a kernel module (leds-gca230718) to access the LEDs via DTS - The LED controller supports PWM for brightness control and ramp control for smooth blinking. This is not implemented in the driver - The LED controller supports toggling (on -> off -> on -> off) where the brightness of the LEDs can be set individually for each on cycle - Until now, only simple active/inactive control is implemented (like when the LEDs would have been connected via GPIO) - Controlling the LEDs requires three sequences sent to the chip. Each sequence consists of - A reset command (0x81 0xE4) written to register 0x00 - A control command (for example 0x0C 0x02 0x01 0x00 0x00 0x00 0xFF 0x01 0x00 0x00 0x00 0xFF 0x87 written to register 0x03) - The reset command is always the same - In the control command - byte 0 is always the same - byte 1 (0x02 in the example above) must be changed in every sequence: 0x02 -> 0x01 -> 0x03) - byte 2 is set to 0x01 which disables toggling. 0x02 would be LED toggling without ramp control, 0x03 would be toggling with ramp control - byte 3 to 6 define the brightness values for the LEDs (R,G,B,W) for the first on cycle when toggling - byte 7 defines the toggling frequency (if toggling enabled) - byte 8 to 11 define the brightness values for the LEDs (R,G,B,W) for the second on cycle when toggling - byte 12 is constant 0x87 Signed-off-by: Roland Reinl (cherry picked from commit 0682974aa8da4855ca373d195cb6365913fc1f8e) --- package/kernel/leds-gca230718/Makefile | 34 +++ package/kernel/leds-gca230718/src/Makefile | 1 + .../leds-gca230718/src/leds-gca230718.c | 227 ++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 package/kernel/leds-gca230718/Makefile create mode 100644 package/kernel/leds-gca230718/src/Makefile create mode 100644 package/kernel/leds-gca230718/src/leds-gca230718.c diff --git a/package/kernel/leds-gca230718/Makefile b/package/kernel/leds-gca230718/Makefile new file mode 100644 index 0000000000..a1ae3bc319 --- /dev/null +++ b/package/kernel/leds-gca230718/Makefile @@ -0,0 +1,34 @@ +# +# Copyright (C) 2008-2010 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=leds-gca230718 +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-2.0 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/leds-gca230718 + SUBMENU:=LED modules + TITLE:=GCA230718 LED support (e.g. for D-Link M30) + FILES:= \ + $(PKG_BUILD_DIR)/leds-gca230718.ko + AUTOLOAD:=$(call AutoProbe,leds-gca230718,1) + DEPENDS:=@TARGET_mediatek_filogic +endef + +define KernelPackage/leds-gca230718/description + GCA230718 LED support (e.g. for D-Link M30) using I2C. +endef + +define Build/Compile + $(KERNEL_MAKE) M="$(PKG_BUILD_DIR)" modules +endef + +$(eval $(call KernelPackage,leds-gca230718)) diff --git a/package/kernel/leds-gca230718/src/Makefile b/package/kernel/leds-gca230718/src/Makefile new file mode 100644 index 0000000000..eaa4eef219 --- /dev/null +++ b/package/kernel/leds-gca230718/src/Makefile @@ -0,0 +1 @@ +obj-m := leds-gca230718.o diff --git a/package/kernel/leds-gca230718/src/leds-gca230718.c b/package/kernel/leds-gca230718/src/leds-gca230718.c new file mode 100644 index 0000000000..d759453cd9 --- /dev/null +++ b/package/kernel/leds-gca230718/src/leds-gca230718.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GCA230718 LED support (e.g. for D-Link M30) using I2C + * + * Copyright 2022 Roland Reinl + * + * This driver can control RGBW LEDs which are connected to a GCA230718. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GCA230718_MAX_LEDS (4u) + +#define GCA230718_OPMODE_DISABLED (0x00u) +#define GCA230718_OPMODE_NO_TOGGLE (0x01u) +#define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_DISABLED (0x02u) +#define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_ENSABLED (0x03u) + +#define GCA230718_1ST_SEQUENCE_BYTE_1 (0x02u) +#define GCA230718_2ND_SEQUENCE_BYTE_1 (0x01u) +#define GCA230718_3RD_SEQUENCE_BYTE_1 (0x03u) + +struct gca230718_led +{ + enum led_brightness brightness; + struct i2c_client *client; + struct led_classdev ledClassDev; +}; + +struct gca230718_private +{ + struct mutex lock; + struct gca230718_led leds[GCA230718_MAX_LEDS]; +}; + +static void gca230718_init_private_led_data(struct gca230718_private* data) +{ + u8 ledIndex; + for (ledIndex = 0; ledIndex < GCA230718_MAX_LEDS; ledIndex++) + { + data->leds[ledIndex].client = NULL; + } +} + +static void gca230718_send_sequence(struct i2c_client *client, u8 byte0, struct gca230718_private* gca230718_privateData) +{ + int status = 0; + u8 ledIndex; + const u8 resetCommand[2] = { 0x81, 0xE4 }; + const u8 resetCommandRegister = 0x00; + + u8 controlCommand[13]; + const u8 controlCommandRegister = 0x03; + + controlCommand[0] = 0x0C; /* Unknown */ + controlCommand[1] = byte0; + controlCommand[2] = GCA230718_OPMODE_NO_TOGGLE; + /* Byte 3-6 are set below to the brighness value of the individual LEDs */ + controlCommand[7] = 0x01; /* Frequency, doesn't care as long as GCA230718_OPMODE_NO_TOGGLE is used above */ + /* Byte 8-11 are set below to the brighness value of the individual LEDs */ + controlCommand[12] = 0x87; + + for (ledIndex = 0; ledIndex < GCA230718_MAX_LEDS; ledIndex++) + { + controlCommand[3 + ledIndex] = gca230718_privateData->leds[ledIndex].brightness; + controlCommand[8 + ledIndex] = gca230718_privateData->leds[ledIndex].brightness; + } + + mutex_lock(&(gca230718_privateData->lock)); + + if ((status = i2c_smbus_write_i2c_block_data(client, resetCommandRegister, sizeof(resetCommand), resetCommand)) != 0) + { + pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for reset command\n", status); + } + else if ((status = i2c_smbus_write_i2c_block_data(client, controlCommandRegister, sizeof(controlCommand), controlCommand)) != 0) + { + pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for control command\n", status); + } + + mutex_unlock(&(gca230718_privateData->lock)); +} + +static int gca230718_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) +{ + struct gca230718_led* led; + struct i2c_client* client; + + led = container_of(led_cdev, struct gca230718_led, ledClassDev); + client = led->client; + + if (client != NULL) + { + struct gca230718_private* gca230718_privateData; + + led->brightness = value; + gca230718_privateData = i2c_get_clientdata(client); + + gca230718_send_sequence(client, GCA230718_2ND_SEQUENCE_BYTE_1, gca230718_privateData); + } + + return 0; +} + +static int gca230718_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int status = 0; + struct gca230718_private* gca230718_privateData; + + pr_info("Enter gca230718_probe for device address %u\n", client->addr); + gca230718_privateData = devm_kzalloc(&(client->dev), sizeof(struct gca230718_private), GFP_KERNEL); + + if (gca230718_privateData == NULL) + { + pr_info("Error during allocating memory for private data\n"); + status = -ENOMEM; + } + else + { + struct device_node* ledNode; + mutex_init(&gca230718_privateData->lock); + gca230718_init_private_led_data(gca230718_privateData); + i2c_set_clientdata(client, gca230718_privateData); + + for_each_child_of_node(client->dev.of_node, ledNode) + { + u32 regValue = 0; + if (of_property_read_u32(ledNode, "reg", ®Value) != 0) + { + pr_info("Missing entry \"reg\" in node %s\n", ledNode->name); + } + else if (regValue >= GCA230718_MAX_LEDS) + { + pr_info("Invalid entry \"reg\" in node %s (%u)\n", ledNode->name, regValue); + } + else + { + struct led_classdev* ledClassDev = &(gca230718_privateData->leds[regValue].ledClassDev); + struct led_init_data init_data = {}; + + gca230718_privateData->leds[regValue].client = client; + init_data.fwnode = of_fwnode_handle(ledNode); + + pr_info("Creating LED for node %s: reg=%u\n", ledNode->name, regValue); + + ledClassDev->name = of_get_property(ledNode, "label", NULL); + if (ledClassDev->name == NULL) + { + ledClassDev->name = ledNode->name; + } + + ledClassDev->brightness = LED_OFF; + ledClassDev->max_brightness = LED_FULL; + ledClassDev->brightness_set_blocking = gca230718_set_brightness; + + if (devm_led_classdev_register_ext(&(client->dev), ledClassDev, &init_data) != 0) + { + pr_info("Error during call of devm_led_classdev_register_ext"); + } + } + } + } + + if (status == 0) + { + /* + Send full initialization sequence. + Afterwards only GCA230718_2ND_SEQUENCE_BYTE_1 must be send to upddate the brightness values. + */ + gca230718_send_sequence(client, GCA230718_1ST_SEQUENCE_BYTE_1, gca230718_privateData); + gca230718_send_sequence(client, GCA230718_2ND_SEQUENCE_BYTE_1, gca230718_privateData); + gca230718_send_sequence(client, GCA230718_3RD_SEQUENCE_BYTE_1, gca230718_privateData); + } + + return status; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0) +static void gca230718_remove(struct i2c_client *client) +#else +static int gca230718_remove(struct i2c_client *client) +#endif +{ + struct gca230718_private* gca230718_privateData; + gca230718_privateData = i2c_get_clientdata(client); + mutex_destroy(&gca230718_privateData->lock); + gca230718_init_private_led_data(gca230718_privateData); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) + return 0; +#endif +} + +static const struct i2c_device_id gca230718_i2c_ids[] = { + { "gca230718", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, gca230718_i2c_ids); + +static const struct of_device_id gca230718_dt_ids[] = { + { .compatible = "unknown,gca230718" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gca230718_dt_ids); + +static struct i2c_driver gca230718_driver = { + .probe = gca230718_probe, + .remove = gca230718_remove, + .id_table = gca230718_i2c_ids, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = gca230718_dt_ids, + }, +}; + +module_i2c_driver(gca230718_driver); + +MODULE_AUTHOR("Roland Reinl "); +MODULE_DESCRIPTION("GCA230718 LED support (e.g. for D-Link M30) using I2C"); +MODULE_LICENSE("GPL");