1 // SPDX-License-Identifier: GPL-2.0-only
3 * GCA230718 LED support (e.g. for D-Link M30) using I2C
5 * Copyright 2022 Roland Reinl <reinlroland+github@gmail.com>
7 * This driver can control RGBW LEDs which are connected to a GCA230718.
10 #include <linux/delay.h>
11 #include <linux/led-class-multicolor.h>
12 #include <linux/leds.h>
13 #include <linux/module.h>
14 #include <linux/of_device.h>
15 #include <linux/property.h>
16 #include <linux/i2c.h>
17 #include <linux/mutex.h>
18 #include <linux/version.h>
20 #define GCA230718_MAX_LEDS (4u)
22 #define GCA230718_OPMODE_DISABLED (0x00u)
23 #define GCA230718_OPMODE_NO_TOGGLE (0x01u)
24 #define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_DISABLED (0x02u)
25 #define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_ENSABLED (0x03u)
27 #define GCA230718_1ST_SEQUENCE_BYTE_1 (0x02u)
28 #define GCA230718_2ND_SEQUENCE_BYTE_1 (0x01u)
29 #define GCA230718_3RD_SEQUENCE_BYTE_1 (0x03u)
33 enum led_brightness brightness
;
34 struct i2c_client
*client
;
35 struct led_classdev ledClassDev
;
38 struct gca230718_private
41 struct gca230718_led leds
[GCA230718_MAX_LEDS
];
44 static void gca230718_init_private_led_data(struct gca230718_private
* data
)
47 for (ledIndex
= 0; ledIndex
< GCA230718_MAX_LEDS
; ledIndex
++)
49 data
->leds
[ledIndex
].client
= NULL
;
53 static void gca230718_send_sequence(struct i2c_client
*client
, u8 byte0
, struct gca230718_private
* gca230718_privateData
)
57 const u8 resetCommand
[2] = { 0x81, 0xE4 };
58 const u8 resetCommandRegister
= 0x00;
60 u8 controlCommand
[13];
61 const u8 controlCommandRegister
= 0x03;
63 controlCommand
[0] = 0x0C; /* Unknown */
64 controlCommand
[1] = byte0
;
65 controlCommand
[2] = GCA230718_OPMODE_NO_TOGGLE
;
66 /* Byte 3-6 are set below to the brighness value of the individual LEDs */
67 controlCommand
[7] = 0x01; /* Frequency, doesn't care as long as GCA230718_OPMODE_NO_TOGGLE is used above */
68 /* Byte 8-11 are set below to the brighness value of the individual LEDs */
69 controlCommand
[12] = 0x87;
71 for (ledIndex
= 0; ledIndex
< GCA230718_MAX_LEDS
; ledIndex
++)
73 controlCommand
[3 + ledIndex
] = gca230718_privateData
->leds
[ledIndex
].brightness
;
74 controlCommand
[8 + ledIndex
] = gca230718_privateData
->leds
[ledIndex
].brightness
;
77 mutex_lock(&(gca230718_privateData
->lock
));
79 if ((status
= i2c_smbus_write_i2c_block_data(client
, resetCommandRegister
, sizeof(resetCommand
), resetCommand
)) != 0)
81 pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for reset command\n", status
);
83 else if ((status
= i2c_smbus_write_i2c_block_data(client
, controlCommandRegister
, sizeof(controlCommand
), controlCommand
)) != 0)
85 pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for control command\n", status
);
88 mutex_unlock(&(gca230718_privateData
->lock
));
91 static int gca230718_set_brightness(struct led_classdev
*led_cdev
, enum led_brightness value
)
93 struct gca230718_led
* led
;
94 struct i2c_client
* client
;
96 led
= container_of(led_cdev
, struct gca230718_led
, ledClassDev
);
101 struct gca230718_private
* gca230718_privateData
;
103 led
->brightness
= value
;
104 gca230718_privateData
= i2c_get_clientdata(client
);
106 gca230718_send_sequence(client
, GCA230718_2ND_SEQUENCE_BYTE_1
, gca230718_privateData
);
112 static int gca230718_probe(struct i2c_client
*client
, const struct i2c_device_id
*id
)
115 struct gca230718_private
* gca230718_privateData
;
117 pr_info("Enter gca230718_probe for device address %u\n", client
->addr
);
118 gca230718_privateData
= devm_kzalloc(&(client
->dev
), sizeof(struct gca230718_private
), GFP_KERNEL
);
120 if (gca230718_privateData
== NULL
)
122 pr_info("Error during allocating memory for private data\n");
127 struct device_node
* ledNode
;
128 mutex_init(&gca230718_privateData
->lock
);
129 gca230718_init_private_led_data(gca230718_privateData
);
130 i2c_set_clientdata(client
, gca230718_privateData
);
132 for_each_child_of_node(client
->dev
.of_node
, ledNode
)
135 if (of_property_read_u32(ledNode
, "reg", ®Value
) != 0)
137 pr_info("Missing entry \"reg\" in node %s\n", ledNode
->name
);
139 else if (regValue
>= GCA230718_MAX_LEDS
)
141 pr_info("Invalid entry \"reg\" in node %s (%u)\n", ledNode
->name
, regValue
);
145 struct led_classdev
* ledClassDev
= &(gca230718_privateData
->leds
[regValue
].ledClassDev
);
146 struct led_init_data init_data
= {};
148 gca230718_privateData
->leds
[regValue
].client
= client
;
149 init_data
.fwnode
= of_fwnode_handle(ledNode
);
151 pr_info("Creating LED for node %s: reg=%u\n", ledNode
->name
, regValue
);
153 ledClassDev
->name
= of_get_property(ledNode
, "label", NULL
);
154 if (ledClassDev
->name
== NULL
)
156 ledClassDev
->name
= ledNode
->name
;
159 ledClassDev
->brightness
= LED_OFF
;
160 ledClassDev
->max_brightness
= LED_FULL
;
161 ledClassDev
->brightness_set_blocking
= gca230718_set_brightness
;
163 if (devm_led_classdev_register_ext(&(client
->dev
), ledClassDev
, &init_data
) != 0)
165 pr_info("Error during call of devm_led_classdev_register_ext");
174 Send full initialization sequence.
175 Afterwards only GCA230718_2ND_SEQUENCE_BYTE_1 must be send to upddate the brightness values.
177 gca230718_send_sequence(client
, GCA230718_1ST_SEQUENCE_BYTE_1
, gca230718_privateData
);
178 gca230718_send_sequence(client
, GCA230718_2ND_SEQUENCE_BYTE_1
, gca230718_privateData
);
179 gca230718_send_sequence(client
, GCA230718_3RD_SEQUENCE_BYTE_1
, gca230718_privateData
);
185 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0)
186 static void gca230718_remove(struct i2c_client
*client
)
188 static int gca230718_remove(struct i2c_client
*client
)
191 struct gca230718_private
* gca230718_privateData
;
192 gca230718_privateData
= i2c_get_clientdata(client
);
193 mutex_destroy(&gca230718_privateData
->lock
);
194 gca230718_init_private_led_data(gca230718_privateData
);
196 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0)
201 static const struct i2c_device_id gca230718_i2c_ids
[] = {
205 MODULE_DEVICE_TABLE(i2c
, gca230718_i2c_ids
);
207 static const struct of_device_id gca230718_dt_ids
[] = {
208 { .compatible
= "unknown,gca230718" },
211 MODULE_DEVICE_TABLE(of
, gca230718_dt_ids
);
213 static struct i2c_driver gca230718_driver
= {
214 .probe
= gca230718_probe
,
215 .remove
= gca230718_remove
,
216 .id_table
= gca230718_i2c_ids
,
218 .name
= KBUILD_MODNAME
,
219 .of_match_table
= gca230718_dt_ids
,
223 module_i2c_driver(gca230718_driver
);
225 MODULE_AUTHOR("Roland Reinl <reinlroland+github@gmail.com>");
226 MODULE_DESCRIPTION("GCA230718 LED support (e.g. for D-Link M30) using I2C");
227 MODULE_LICENSE("GPL");