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 struct device_node
*child
;
84 struct device
*dev
= &spi
->dev
;
85 struct spi_byte_led
*led
;
86 const char *name
= "leds-spi-byte::";
90 if (of_get_available_child_count(dev_of_node(dev
)) != 1) {
91 dev_err(dev
, "Device must have exactly one LED sub-node.");
94 child
= of_get_next_available_child(dev_of_node(dev
), NULL
);
96 led
= devm_kzalloc(dev
, sizeof(*led
), GFP_KERNEL
);
100 of_property_read_string(child
, "label", &name
);
101 strlcpy(led
->name
, name
, sizeof(led
->name
));
103 mutex_init(&led
->mutex
);
104 led
->cdef
= device_get_match_data(dev
);
105 led
->ldev
.name
= led
->name
;
106 led
->ldev
.brightness
= LED_OFF
;
107 led
->ldev
.max_brightness
= led
->cdef
->max_value
- led
->cdef
->off_value
;
108 led
->ldev
.brightness_set_blocking
= spi_byte_brightness_set_blocking
;
110 state
= of_get_property(child
, "default-state", NULL
);
112 if (!strcmp(state
, "on")) {
113 led
->ldev
.brightness
= led
->ldev
.max_brightness
;
114 } else if (strcmp(state
, "off")) {
115 /* all other cases except "off" */
116 dev_err(dev
, "default-state can only be 'on' or 'off'");
120 spi_byte_brightness_set_blocking(&led
->ldev
,
121 led
->ldev
.brightness
);
123 ret
= devm_led_classdev_register(&spi
->dev
, &led
->ldev
);
125 mutex_destroy(&led
->mutex
);
128 spi_set_drvdata(spi
, led
);
133 static int spi_byte_remove(struct spi_device
*spi
)
135 struct spi_byte_led
*led
= spi_get_drvdata(spi
);
137 mutex_destroy(&led
->mutex
);
142 static struct spi_driver spi_byte_driver
= {
143 .probe
= spi_byte_probe
,
144 .remove
= spi_byte_remove
,
146 .name
= KBUILD_MODNAME
,
147 .of_match_table
= spi_byte_dt_ids
,
151 module_spi_driver(spi_byte_driver
);
153 MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");
154 MODULE_DESCRIPTION("single byte SPI LED driver");
155 MODULE_LICENSE("GPL v2");
156 MODULE_ALIAS("spi:leds-spi-byte");