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/module.h>
33 #include <linux/of_device.h>
34 #include <linux/spi/spi.h>
35 #include <linux/mutex.h>
36 #include <uapi/linux/uleds.h>
38 struct spi_byte_chipdef
{
39 /* SPI byte that will be send to switch the LED off */
41 /* SPI byte that will be send to switch the LED to maximum brightness */
46 struct led_classdev ldev
;
47 struct spi_device
*spi
;
48 char name
[LED_MAX_NAME_SIZE
];
50 const struct spi_byte_chipdef
*cdef
;
53 static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef
= {
58 static const struct of_device_id spi_byte_dt_ids
[] = {
59 { .compatible
= "ubnt,acb-spi-led", .data
= &ubnt_acb_spi_led_cdef
},
63 MODULE_DEVICE_TABLE(of
, spi_byte_dt_ids
);
65 static int spi_byte_brightness_set_blocking(struct led_classdev
*dev
,
66 enum led_brightness brightness
)
68 struct spi_byte_led
*led
= container_of(dev
, struct spi_byte_led
, ldev
);
72 value
= (u8
) brightness
+ led
->cdef
->off_value
;
74 mutex_lock(&led
->mutex
);
75 ret
= spi_write(led
->spi
, &value
, sizeof(value
));
76 mutex_unlock(&led
->mutex
);
81 static int spi_byte_probe(struct spi_device
*spi
)
83 const struct of_device_id
*of_dev_id
;
84 struct device_node
*child
;
85 struct device
*dev
= &spi
->dev
;
86 struct spi_byte_led
*led
;
87 const char *name
= "leds-spi-byte::";
91 of_dev_id
= of_match_device(spi_byte_dt_ids
, dev
);
95 if (of_get_child_count(dev
->of_node
) != 1) {
96 dev_err(dev
, "Device must have exactly one LED sub-node.");
99 child
= of_get_next_child(dev
->of_node
, NULL
);
101 led
= devm_kzalloc(dev
, sizeof(*led
), GFP_KERNEL
);
105 of_property_read_string(child
, "label", &name
);
106 strlcpy(led
->name
, name
, sizeof(led
->name
));
108 mutex_init(&led
->mutex
);
109 led
->cdef
= of_dev_id
->data
;
110 led
->ldev
.name
= led
->name
;
111 led
->ldev
.brightness
= LED_OFF
;
112 led
->ldev
.max_brightness
= led
->cdef
->max_value
- led
->cdef
->off_value
;
113 led
->ldev
.brightness_set_blocking
= spi_byte_brightness_set_blocking
;
115 state
= of_get_property(child
, "default-state", NULL
);
117 if (!strcmp(state
, "on")) {
118 led
->ldev
.brightness
= led
->ldev
.max_brightness
;
119 } else if (strcmp(state
, "off")) {
120 /* all other cases except "off" */
121 dev_err(dev
, "default-state can only be 'on' or 'off'");
125 spi_byte_brightness_set_blocking(&led
->ldev
,
126 led
->ldev
.brightness
);
128 ret
= devm_led_classdev_register(&spi
->dev
, &led
->ldev
);
130 mutex_destroy(&led
->mutex
);
133 spi_set_drvdata(spi
, led
);
138 static int spi_byte_remove(struct spi_device
*spi
)
140 struct spi_byte_led
*led
= spi_get_drvdata(spi
);
142 mutex_destroy(&led
->mutex
);
147 static struct spi_driver spi_byte_driver
= {
148 .probe
= spi_byte_probe
,
149 .remove
= spi_byte_remove
,
151 .name
= KBUILD_MODNAME
,
152 .of_match_table
= spi_byte_dt_ids
,
156 module_spi_driver(spi_byte_driver
);
158 MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");
159 MODULE_DESCRIPTION("single byte SPI LED driver");
160 MODULE_LICENSE("GPL v2");
161 MODULE_ALIAS("spi:leds-spi-byte");