1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2019 Christian Mauderer <oss@c-mauderer.de>
5 * The driver supports controllers with a very simple SPI protocol:
6 * - one LED is controlled by a single byte on MOSI
7 * - the value of the byte gives the brightness between two values (lowest to
9 * - no return value is necessary (no MISO signal)
11 * The value for minimum and maximum brightness depends on the device
12 * (compatible string).
15 * - "ubnt,acb-spi-led": Microcontroller (SONiX 8F26E611LA) based device used
16 * for example in Ubiquiti airCube ISP. Reverse engineered protocol for this
18 * * Higher two bits set a mode. Lower six bits are a parameter.
19 * * Mode: 00 -> set brightness between 0x00 (min) and 0x3F (max)
20 * * Mode: 01 -> pulsing pattern (min -> max -> min) with an interval. From
21 * some tests, the period is about (50ms + 102ms * parameter). There is a
22 * slightly different pattern starting from 0x10 (longer gap between the
23 * pulses) but the time still follows that calculation.
24 * * Mode: 10 -> same as 01 but with only a ramp from min to max. Again a
25 * slight jump in the pattern at 0x10.
26 * * Mode: 11 -> blinking (off -> 25% -> off -> 25% -> ...) with a period of
28 * NOTE: This driver currently only supports mode 00.
31 #include <linux/leds.h>
32 #include <linux/mod_devicetable.h>
33 #include <linux/module.h>
34 #include <linux/mutex.h>
35 #include <linux/property.h>
36 #include <linux/spi/spi.h>
37 #include <uapi/linux/uleds.h>
39 struct spi_byte_chipdef
{
40 /* SPI byte that will be send to switch the LED off */
42 /* SPI byte that will be send to switch the LED to maximum brightness */
47 struct led_classdev ldev
;
48 struct spi_device
*spi
;
49 char name
[LED_MAX_NAME_SIZE
];
51 const struct spi_byte_chipdef
*cdef
;
54 static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef
= {
59 static int spi_byte_brightness_set_blocking(struct led_classdev
*dev
,
60 enum led_brightness brightness
)
62 struct spi_byte_led
*led
= container_of(dev
, struct spi_byte_led
, ldev
);
66 value
= (u8
) brightness
+ led
->cdef
->off_value
;
68 mutex_lock(&led
->mutex
);
69 ret
= spi_write(led
->spi
, &value
, sizeof(value
));
70 mutex_unlock(&led
->mutex
);
75 static int spi_byte_probe(struct spi_device
*spi
)
77 struct fwnode_handle
*child
__free(fwnode_handle
) = NULL
;
78 struct device
*dev
= &spi
->dev
;
79 struct spi_byte_led
*led
;
80 struct led_init_data init_data
= {};
81 enum led_default_state state
;
84 if (device_get_child_node_count(dev
) != 1) {
85 dev_err(dev
, "Device must have exactly one LED sub-node.");
89 led
= devm_kzalloc(dev
, sizeof(*led
), GFP_KERNEL
);
93 ret
= devm_mutex_init(dev
, &led
->mutex
);
98 led
->cdef
= device_get_match_data(dev
);
99 led
->ldev
.brightness
= LED_OFF
;
100 led
->ldev
.max_brightness
= led
->cdef
->max_value
- led
->cdef
->off_value
;
101 led
->ldev
.brightness_set_blocking
= spi_byte_brightness_set_blocking
;
103 child
= device_get_next_child_node(dev
, NULL
);
105 state
= led_init_default_state_get(child
);
106 if (state
== LEDS_DEFSTATE_ON
)
107 led
->ldev
.brightness
= led
->ldev
.max_brightness
;
108 spi_byte_brightness_set_blocking(&led
->ldev
,
109 led
->ldev
.brightness
);
111 init_data
.fwnode
= child
;
112 init_data
.devicename
= "leds-spi-byte";
113 init_data
.default_label
= ":";
115 return devm_led_classdev_register_ext(dev
, &led
->ldev
, &init_data
);
118 static const struct of_device_id spi_byte_dt_ids
[] = {
119 { .compatible
= "ubnt,acb-spi-led", .data
= &ubnt_acb_spi_led_cdef
},
122 MODULE_DEVICE_TABLE(of
, spi_byte_dt_ids
);
124 static struct spi_driver spi_byte_driver
= {
125 .probe
= spi_byte_probe
,
127 .name
= KBUILD_MODNAME
,
128 .of_match_table
= spi_byte_dt_ids
,
131 module_spi_driver(spi_byte_driver
);
133 MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");
134 MODULE_DESCRIPTION("single byte SPI LED driver");
135 MODULE_LICENSE("GPL v2");
136 MODULE_ALIAS("spi:leds-spi-byte");