Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 // Copyright (c) 2019 Christian Mauderer <oss@c-mauderer.de>
0003 
0004 /*
0005  * The driver supports controllers with a very simple SPI protocol:
0006  * - one LED is controlled by a single byte on MOSI
0007  * - the value of the byte gives the brightness between two values (lowest to
0008  *   highest)
0009  * - no return value is necessary (no MISO signal)
0010  *
0011  * The value for minimum and maximum brightness depends on the device
0012  * (compatible string).
0013  *
0014  * Supported devices:
0015  * - "ubnt,acb-spi-led": Microcontroller (SONiX 8F26E611LA) based device used
0016  *   for example in Ubiquiti airCube ISP. Reverse engineered protocol for this
0017  *   controller:
0018  *   * Higher two bits set a mode. Lower six bits are a parameter.
0019  *   * Mode: 00 -> set brightness between 0x00 (min) and 0x3F (max)
0020  *   * Mode: 01 -> pulsing pattern (min -> max -> min) with an interval. From
0021  *     some tests, the period is about (50ms + 102ms * parameter). There is a
0022  *     slightly different pattern starting from 0x10 (longer gap between the
0023  *     pulses) but the time still follows that calculation.
0024  *   * Mode: 10 -> same as 01 but with only a ramp from min to max. Again a
0025  *     slight jump in the pattern at 0x10.
0026  *   * Mode: 11 -> blinking (off -> 25% -> off -> 25% -> ...) with a period of
0027  *     (105ms * parameter)
0028  *   NOTE: This driver currently only supports mode 00.
0029  */
0030 
0031 #include <linux/leds.h>
0032 #include <linux/module.h>
0033 #include <linux/of_device.h>
0034 #include <linux/spi/spi.h>
0035 #include <linux/mutex.h>
0036 #include <uapi/linux/uleds.h>
0037 
0038 struct spi_byte_chipdef {
0039     /* SPI byte that will be send to switch the LED off */
0040     u8  off_value;
0041     /* SPI byte that will be send to switch the LED to maximum brightness */
0042     u8  max_value;
0043 };
0044 
0045 struct spi_byte_led {
0046     struct led_classdev     ldev;
0047     struct spi_device       *spi;
0048     char                name[LED_MAX_NAME_SIZE];
0049     struct mutex            mutex;
0050     const struct spi_byte_chipdef   *cdef;
0051 };
0052 
0053 static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = {
0054     .off_value = 0x0,
0055     .max_value = 0x3F,
0056 };
0057 
0058 static const struct of_device_id spi_byte_dt_ids[] = {
0059     { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef },
0060     {},
0061 };
0062 
0063 MODULE_DEVICE_TABLE(of, spi_byte_dt_ids);
0064 
0065 static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
0066                         enum led_brightness brightness)
0067 {
0068     struct spi_byte_led *led = container_of(dev, struct spi_byte_led, ldev);
0069     u8 value;
0070     int ret;
0071 
0072     value = (u8) brightness + led->cdef->off_value;
0073 
0074     mutex_lock(&led->mutex);
0075     ret = spi_write(led->spi, &value, sizeof(value));
0076     mutex_unlock(&led->mutex);
0077 
0078     return ret;
0079 }
0080 
0081 static int spi_byte_probe(struct spi_device *spi)
0082 {
0083     struct device_node *child;
0084     struct device *dev = &spi->dev;
0085     struct spi_byte_led *led;
0086     const char *name = "leds-spi-byte::";
0087     const char *state;
0088     int ret;
0089 
0090     if (of_get_available_child_count(dev_of_node(dev)) != 1) {
0091         dev_err(dev, "Device must have exactly one LED sub-node.");
0092         return -EINVAL;
0093     }
0094     child = of_get_next_available_child(dev_of_node(dev), NULL);
0095 
0096     led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
0097     if (!led)
0098         return -ENOMEM;
0099 
0100     of_property_read_string(child, "label", &name);
0101     strlcpy(led->name, name, sizeof(led->name));
0102     led->spi = spi;
0103     mutex_init(&led->mutex);
0104     led->cdef = device_get_match_data(dev);
0105     led->ldev.name = led->name;
0106     led->ldev.brightness = LED_OFF;
0107     led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value;
0108     led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking;
0109 
0110     state = of_get_property(child, "default-state", NULL);
0111     if (state) {
0112         if (!strcmp(state, "on")) {
0113             led->ldev.brightness = led->ldev.max_brightness;
0114         } else if (strcmp(state, "off")) {
0115             /* all other cases except "off" */
0116             dev_err(dev, "default-state can only be 'on' or 'off'");
0117             return -EINVAL;
0118         }
0119     }
0120     spi_byte_brightness_set_blocking(&led->ldev,
0121                      led->ldev.brightness);
0122 
0123     ret = devm_led_classdev_register(&spi->dev, &led->ldev);
0124     if (ret) {
0125         mutex_destroy(&led->mutex);
0126         return ret;
0127     }
0128     spi_set_drvdata(spi, led);
0129 
0130     return 0;
0131 }
0132 
0133 static void spi_byte_remove(struct spi_device *spi)
0134 {
0135     struct spi_byte_led *led = spi_get_drvdata(spi);
0136 
0137     mutex_destroy(&led->mutex);
0138 }
0139 
0140 static struct spi_driver spi_byte_driver = {
0141     .probe      = spi_byte_probe,
0142     .remove     = spi_byte_remove,
0143     .driver = {
0144         .name       = KBUILD_MODNAME,
0145         .of_match_table = spi_byte_dt_ids,
0146     },
0147 };
0148 
0149 module_spi_driver(spi_byte_driver);
0150 
0151 MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");
0152 MODULE_DESCRIPTION("single byte SPI LED driver");
0153 MODULE_LICENSE("GPL v2");
0154 MODULE_ALIAS("spi:leds-spi-byte");