19e50d5fbSOleh Kravchenko // SPDX-License-Identifier: GPL-2.0
29e50d5fbSOleh Kravchenko // Copyright (c) 2018 Crane Merchandising Systems. All rights reserved.
39e50d5fbSOleh Kravchenko // Copyright (C) 2018 Oleh Kravchenko <oleg@kaa.org.ua>
49e50d5fbSOleh Kravchenko
59e50d5fbSOleh Kravchenko #include <linux/delay.h>
69e50d5fbSOleh Kravchenko #include <linux/leds.h>
7*3192f141SRob Herring #include <linux/mod_devicetable.h>
89e50d5fbSOleh Kravchenko #include <linux/module.h>
99e50d5fbSOleh Kravchenko #include <linux/spi/spi.h>
109e50d5fbSOleh Kravchenko #include <linux/workqueue.h>
119e50d5fbSOleh Kravchenko
129e50d5fbSOleh Kravchenko /*
139e50d5fbSOleh Kravchenko * CR0014114 SPI protocol descrtiption:
149e50d5fbSOleh Kravchenko * +----+-----------------------------------+----+
159e50d5fbSOleh Kravchenko * | CMD| BRIGHTNESS |CRC |
169e50d5fbSOleh Kravchenko * +----+-----------------------------------+----+
179e50d5fbSOleh Kravchenko * | | LED0| LED1| LED2| LED3| LED4| LED5| |
189e50d5fbSOleh Kravchenko * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
199e50d5fbSOleh Kravchenko * | |R|G|B|R|G|B|R|G|B|R|G|B|R|G|B|R|G|B| |
209e50d5fbSOleh Kravchenko * | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 |
219e50d5fbSOleh Kravchenko * | |1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| |
229e50d5fbSOleh Kravchenko * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
239e50d5fbSOleh Kravchenko * | | 18 | |
249e50d5fbSOleh Kravchenko * +----+-----------------------------------+----+
259e50d5fbSOleh Kravchenko * | 20 |
269e50d5fbSOleh Kravchenko * +---------------------------------------------+
279e50d5fbSOleh Kravchenko *
289e50d5fbSOleh Kravchenko * PS: Boards can be connected to the chain:
299e50d5fbSOleh Kravchenko * SPI -> board0 -> board1 -> board2 ..
309e50d5fbSOleh Kravchenko */
319e50d5fbSOleh Kravchenko
329e50d5fbSOleh Kravchenko /* CR0014114 SPI commands */
339e50d5fbSOleh Kravchenko #define CR_SET_BRIGHTNESS 0x80
349e50d5fbSOleh Kravchenko #define CR_INIT_REENUMERATE 0x81
359e50d5fbSOleh Kravchenko #define CR_NEXT_REENUMERATE 0x82
369e50d5fbSOleh Kravchenko
379e50d5fbSOleh Kravchenko /* CR0014114 default settings */
389e50d5fbSOleh Kravchenko #define CR_MAX_BRIGHTNESS GENMASK(6, 0)
399e50d5fbSOleh Kravchenko #define CR_FW_DELAY_MSEC 10
409e50d5fbSOleh Kravchenko #define CR_RECOUNT_DELAY (HZ * 3600)
419e50d5fbSOleh Kravchenko
42889003c2SJacek Anaszewski #define CR_DEV_NAME "cr0014114"
43889003c2SJacek Anaszewski
449e50d5fbSOleh Kravchenko struct cr0014114_led {
459e50d5fbSOleh Kravchenko struct cr0014114 *priv;
469e50d5fbSOleh Kravchenko struct led_classdev ldev;
479e50d5fbSOleh Kravchenko u8 brightness;
489e50d5fbSOleh Kravchenko };
499e50d5fbSOleh Kravchenko
509e50d5fbSOleh Kravchenko struct cr0014114 {
519e50d5fbSOleh Kravchenko bool do_recount;
529e50d5fbSOleh Kravchenko size_t count;
539e50d5fbSOleh Kravchenko struct delayed_work work;
549e50d5fbSOleh Kravchenko struct device *dev;
559e50d5fbSOleh Kravchenko struct mutex lock;
569e50d5fbSOleh Kravchenko struct spi_device *spi;
579e50d5fbSOleh Kravchenko u8 *buf;
589e50d5fbSOleh Kravchenko unsigned long delay;
599e50d5fbSOleh Kravchenko struct cr0014114_led leds[];
609e50d5fbSOleh Kravchenko };
619e50d5fbSOleh Kravchenko
cr0014114_calc_crc(u8 * buf,const size_t len)629e50d5fbSOleh Kravchenko static void cr0014114_calc_crc(u8 *buf, const size_t len)
639e50d5fbSOleh Kravchenko {
649e50d5fbSOleh Kravchenko size_t i;
659e50d5fbSOleh Kravchenko u8 crc;
669e50d5fbSOleh Kravchenko
679e50d5fbSOleh Kravchenko for (i = 1, crc = 1; i < len - 1; i++)
689e50d5fbSOleh Kravchenko crc += buf[i];
699e50d5fbSOleh Kravchenko crc |= BIT(7);
709e50d5fbSOleh Kravchenko
719e50d5fbSOleh Kravchenko /* special case when CRC matches the SPI commands */
729e50d5fbSOleh Kravchenko if (crc == CR_SET_BRIGHTNESS ||
739e50d5fbSOleh Kravchenko crc == CR_INIT_REENUMERATE ||
749e50d5fbSOleh Kravchenko crc == CR_NEXT_REENUMERATE)
759e50d5fbSOleh Kravchenko crc = 0xfe;
769e50d5fbSOleh Kravchenko
779e50d5fbSOleh Kravchenko buf[len - 1] = crc;
789e50d5fbSOleh Kravchenko }
799e50d5fbSOleh Kravchenko
cr0014114_recount(struct cr0014114 * priv)809e50d5fbSOleh Kravchenko static int cr0014114_recount(struct cr0014114 *priv)
819e50d5fbSOleh Kravchenko {
829e50d5fbSOleh Kravchenko int ret;
839e50d5fbSOleh Kravchenko size_t i;
849e50d5fbSOleh Kravchenko u8 cmd;
859e50d5fbSOleh Kravchenko
869e50d5fbSOleh Kravchenko dev_dbg(priv->dev, "LEDs recount is started\n");
879e50d5fbSOleh Kravchenko
889e50d5fbSOleh Kravchenko cmd = CR_INIT_REENUMERATE;
899e50d5fbSOleh Kravchenko ret = spi_write(priv->spi, &cmd, sizeof(cmd));
909e50d5fbSOleh Kravchenko if (ret)
919e50d5fbSOleh Kravchenko goto err;
929e50d5fbSOleh Kravchenko
939e50d5fbSOleh Kravchenko cmd = CR_NEXT_REENUMERATE;
949e50d5fbSOleh Kravchenko for (i = 0; i < priv->count; i++) {
959e50d5fbSOleh Kravchenko msleep(CR_FW_DELAY_MSEC);
969e50d5fbSOleh Kravchenko
979e50d5fbSOleh Kravchenko ret = spi_write(priv->spi, &cmd, sizeof(cmd));
989e50d5fbSOleh Kravchenko if (ret)
999e50d5fbSOleh Kravchenko goto err;
1009e50d5fbSOleh Kravchenko }
1019e50d5fbSOleh Kravchenko
1029e50d5fbSOleh Kravchenko err:
1039e50d5fbSOleh Kravchenko dev_dbg(priv->dev, "LEDs recount is finished\n");
1049e50d5fbSOleh Kravchenko
1059e50d5fbSOleh Kravchenko if (ret)
1069e50d5fbSOleh Kravchenko dev_err(priv->dev, "with error %d", ret);
1079e50d5fbSOleh Kravchenko
1089e50d5fbSOleh Kravchenko return ret;
1099e50d5fbSOleh Kravchenko }
1109e50d5fbSOleh Kravchenko
cr0014114_sync(struct cr0014114 * priv)1119e50d5fbSOleh Kravchenko static int cr0014114_sync(struct cr0014114 *priv)
1129e50d5fbSOleh Kravchenko {
1139e50d5fbSOleh Kravchenko int ret;
1149e50d5fbSOleh Kravchenko size_t i;
1159e50d5fbSOleh Kravchenko unsigned long udelay, now = jiffies;
1169e50d5fbSOleh Kravchenko
1179e50d5fbSOleh Kravchenko /* to avoid SPI mistiming with firmware we should wait some time */
1189e50d5fbSOleh Kravchenko if (time_after(priv->delay, now)) {
1199e50d5fbSOleh Kravchenko udelay = jiffies_to_usecs(priv->delay - now);
1209e50d5fbSOleh Kravchenko usleep_range(udelay, udelay + 1);
1219e50d5fbSOleh Kravchenko }
1229e50d5fbSOleh Kravchenko
1239e50d5fbSOleh Kravchenko if (unlikely(priv->do_recount)) {
1249e50d5fbSOleh Kravchenko ret = cr0014114_recount(priv);
1259e50d5fbSOleh Kravchenko if (ret)
1269e50d5fbSOleh Kravchenko goto err;
1279e50d5fbSOleh Kravchenko
1289e50d5fbSOleh Kravchenko priv->do_recount = false;
1299e50d5fbSOleh Kravchenko msleep(CR_FW_DELAY_MSEC);
1309e50d5fbSOleh Kravchenko }
1319e50d5fbSOleh Kravchenko
1329e50d5fbSOleh Kravchenko priv->buf[0] = CR_SET_BRIGHTNESS;
1339e50d5fbSOleh Kravchenko for (i = 0; i < priv->count; i++)
1349e50d5fbSOleh Kravchenko priv->buf[i + 1] = priv->leds[i].brightness;
1359e50d5fbSOleh Kravchenko cr0014114_calc_crc(priv->buf, priv->count + 2);
1369e50d5fbSOleh Kravchenko ret = spi_write(priv->spi, priv->buf, priv->count + 2);
1379e50d5fbSOleh Kravchenko
1389e50d5fbSOleh Kravchenko err:
1399e50d5fbSOleh Kravchenko priv->delay = jiffies + msecs_to_jiffies(CR_FW_DELAY_MSEC);
1409e50d5fbSOleh Kravchenko
1419e50d5fbSOleh Kravchenko return ret;
1429e50d5fbSOleh Kravchenko }
1439e50d5fbSOleh Kravchenko
cr0014114_recount_work(struct work_struct * work)1449e50d5fbSOleh Kravchenko static void cr0014114_recount_work(struct work_struct *work)
1459e50d5fbSOleh Kravchenko {
1469e50d5fbSOleh Kravchenko int ret;
1479e50d5fbSOleh Kravchenko struct cr0014114 *priv = container_of(work,
1489e50d5fbSOleh Kravchenko struct cr0014114,
1499e50d5fbSOleh Kravchenko work.work);
1509e50d5fbSOleh Kravchenko
1519e50d5fbSOleh Kravchenko mutex_lock(&priv->lock);
1529e50d5fbSOleh Kravchenko priv->do_recount = true;
1539e50d5fbSOleh Kravchenko ret = cr0014114_sync(priv);
1549e50d5fbSOleh Kravchenko mutex_unlock(&priv->lock);
1559e50d5fbSOleh Kravchenko
1569e50d5fbSOleh Kravchenko if (ret)
1579e50d5fbSOleh Kravchenko dev_warn(priv->dev, "sync of LEDs failed %d\n", ret);
1589e50d5fbSOleh Kravchenko
1599e50d5fbSOleh Kravchenko schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY);
1609e50d5fbSOleh Kravchenko }
1619e50d5fbSOleh Kravchenko
cr0014114_set_sync(struct led_classdev * ldev,enum led_brightness brightness)1629e50d5fbSOleh Kravchenko static int cr0014114_set_sync(struct led_classdev *ldev,
1639e50d5fbSOleh Kravchenko enum led_brightness brightness)
1649e50d5fbSOleh Kravchenko {
1659e50d5fbSOleh Kravchenko int ret;
1669e50d5fbSOleh Kravchenko struct cr0014114_led *led = container_of(ldev,
1679e50d5fbSOleh Kravchenko struct cr0014114_led,
1689e50d5fbSOleh Kravchenko ldev);
1699e50d5fbSOleh Kravchenko
170889003c2SJacek Anaszewski dev_dbg(led->priv->dev, "Set brightness to %d\n", brightness);
1719e50d5fbSOleh Kravchenko
1729e50d5fbSOleh Kravchenko mutex_lock(&led->priv->lock);
1739e50d5fbSOleh Kravchenko led->brightness = (u8)brightness;
1749e50d5fbSOleh Kravchenko ret = cr0014114_sync(led->priv);
1759e50d5fbSOleh Kravchenko mutex_unlock(&led->priv->lock);
1769e50d5fbSOleh Kravchenko
1779e50d5fbSOleh Kravchenko return ret;
1789e50d5fbSOleh Kravchenko }
1799e50d5fbSOleh Kravchenko
cr0014114_probe_dt(struct cr0014114 * priv)1809e50d5fbSOleh Kravchenko static int cr0014114_probe_dt(struct cr0014114 *priv)
1819e50d5fbSOleh Kravchenko {
1829e50d5fbSOleh Kravchenko size_t i = 0;
1839e50d5fbSOleh Kravchenko struct cr0014114_led *led;
1849e50d5fbSOleh Kravchenko struct fwnode_handle *child;
185889003c2SJacek Anaszewski struct led_init_data init_data = {};
1869e50d5fbSOleh Kravchenko int ret;
1879e50d5fbSOleh Kravchenko
1889e50d5fbSOleh Kravchenko device_for_each_child_node(priv->dev, child) {
1899e50d5fbSOleh Kravchenko led = &priv->leds[i];
1909e50d5fbSOleh Kravchenko
1919e50d5fbSOleh Kravchenko led->priv = priv;
1929e50d5fbSOleh Kravchenko led->ldev.max_brightness = CR_MAX_BRIGHTNESS;
1939e50d5fbSOleh Kravchenko led->ldev.brightness_set_blocking = cr0014114_set_sync;
1949e50d5fbSOleh Kravchenko
195889003c2SJacek Anaszewski init_data.fwnode = child;
196889003c2SJacek Anaszewski init_data.devicename = CR_DEV_NAME;
197889003c2SJacek Anaszewski init_data.default_label = ":";
198889003c2SJacek Anaszewski
199889003c2SJacek Anaszewski ret = devm_led_classdev_register_ext(priv->dev, &led->ldev,
200889003c2SJacek Anaszewski &init_data);
2019e50d5fbSOleh Kravchenko if (ret) {
2029e50d5fbSOleh Kravchenko dev_err(priv->dev,
203889003c2SJacek Anaszewski "failed to register LED device, err %d", ret);
2049e50d5fbSOleh Kravchenko fwnode_handle_put(child);
2059e50d5fbSOleh Kravchenko return ret;
2069e50d5fbSOleh Kravchenko }
2079e50d5fbSOleh Kravchenko
2089e50d5fbSOleh Kravchenko i++;
2099e50d5fbSOleh Kravchenko }
2109e50d5fbSOleh Kravchenko
2119e50d5fbSOleh Kravchenko return 0;
2129e50d5fbSOleh Kravchenko }
2139e50d5fbSOleh Kravchenko
cr0014114_probe(struct spi_device * spi)2149e50d5fbSOleh Kravchenko static int cr0014114_probe(struct spi_device *spi)
2159e50d5fbSOleh Kravchenko {
2169e50d5fbSOleh Kravchenko struct cr0014114 *priv;
2179e50d5fbSOleh Kravchenko size_t count;
2189e50d5fbSOleh Kravchenko int ret;
2199e50d5fbSOleh Kravchenko
2209e50d5fbSOleh Kravchenko count = device_get_child_node_count(&spi->dev);
2219e50d5fbSOleh Kravchenko if (!count) {
2229e50d5fbSOleh Kravchenko dev_err(&spi->dev, "LEDs are not defined in device tree!");
2239e50d5fbSOleh Kravchenko return -ENODEV;
2249e50d5fbSOleh Kravchenko }
2259e50d5fbSOleh Kravchenko
226f3278e3fSKees Cook priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count),
2279e50d5fbSOleh Kravchenko GFP_KERNEL);
2289e50d5fbSOleh Kravchenko if (!priv)
2299e50d5fbSOleh Kravchenko return -ENOMEM;
2309e50d5fbSOleh Kravchenko
2319e50d5fbSOleh Kravchenko priv->buf = devm_kzalloc(&spi->dev, count + 2, GFP_KERNEL);
2329e50d5fbSOleh Kravchenko if (!priv->buf)
2339e50d5fbSOleh Kravchenko return -ENOMEM;
2349e50d5fbSOleh Kravchenko
2359e50d5fbSOleh Kravchenko mutex_init(&priv->lock);
2369e50d5fbSOleh Kravchenko INIT_DELAYED_WORK(&priv->work, cr0014114_recount_work);
2379e50d5fbSOleh Kravchenko priv->count = count;
2389e50d5fbSOleh Kravchenko priv->dev = &spi->dev;
2399e50d5fbSOleh Kravchenko priv->spi = spi;
2409e50d5fbSOleh Kravchenko priv->delay = jiffies -
2419e50d5fbSOleh Kravchenko msecs_to_jiffies(CR_FW_DELAY_MSEC);
2429e50d5fbSOleh Kravchenko
2439e50d5fbSOleh Kravchenko priv->do_recount = true;
2449e50d5fbSOleh Kravchenko ret = cr0014114_sync(priv);
2459e50d5fbSOleh Kravchenko if (ret) {
2469e50d5fbSOleh Kravchenko dev_err(priv->dev, "first recount failed %d\n", ret);
2479e50d5fbSOleh Kravchenko return ret;
2489e50d5fbSOleh Kravchenko }
2499e50d5fbSOleh Kravchenko
2509e50d5fbSOleh Kravchenko priv->do_recount = true;
2519e50d5fbSOleh Kravchenko ret = cr0014114_sync(priv);
2529e50d5fbSOleh Kravchenko if (ret) {
2539e50d5fbSOleh Kravchenko dev_err(priv->dev, "second recount failed %d\n", ret);
2549e50d5fbSOleh Kravchenko return ret;
2559e50d5fbSOleh Kravchenko }
2569e50d5fbSOleh Kravchenko
2579e50d5fbSOleh Kravchenko ret = cr0014114_probe_dt(priv);
2589e50d5fbSOleh Kravchenko if (ret)
2599e50d5fbSOleh Kravchenko return ret;
2609e50d5fbSOleh Kravchenko
2619e50d5fbSOleh Kravchenko /* setup recount work to workaround buggy firmware */
2629e50d5fbSOleh Kravchenko schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY);
2639e50d5fbSOleh Kravchenko
2649e50d5fbSOleh Kravchenko spi_set_drvdata(spi, priv);
2659e50d5fbSOleh Kravchenko
2669e50d5fbSOleh Kravchenko return 0;
2679e50d5fbSOleh Kravchenko }
2689e50d5fbSOleh Kravchenko
cr0014114_remove(struct spi_device * spi)269a0386bbaSUwe Kleine-König static void cr0014114_remove(struct spi_device *spi)
2709e50d5fbSOleh Kravchenko {
2719e50d5fbSOleh Kravchenko struct cr0014114 *priv = spi_get_drvdata(spi);
2729e50d5fbSOleh Kravchenko
2739e50d5fbSOleh Kravchenko cancel_delayed_work_sync(&priv->work);
2749e50d5fbSOleh Kravchenko mutex_destroy(&priv->lock);
2759e50d5fbSOleh Kravchenko }
2769e50d5fbSOleh Kravchenko
2779e50d5fbSOleh Kravchenko static const struct of_device_id cr0014114_dt_ids[] = {
2789e50d5fbSOleh Kravchenko { .compatible = "crane,cr0014114", },
2799e50d5fbSOleh Kravchenko {},
2809e50d5fbSOleh Kravchenko };
2819e50d5fbSOleh Kravchenko
2829e50d5fbSOleh Kravchenko MODULE_DEVICE_TABLE(of, cr0014114_dt_ids);
2839e50d5fbSOleh Kravchenko
2849e50d5fbSOleh Kravchenko static struct spi_driver cr0014114_driver = {
2859e50d5fbSOleh Kravchenko .probe = cr0014114_probe,
2869e50d5fbSOleh Kravchenko .remove = cr0014114_remove,
2879e50d5fbSOleh Kravchenko .driver = {
2889e50d5fbSOleh Kravchenko .name = KBUILD_MODNAME,
2899e50d5fbSOleh Kravchenko .of_match_table = cr0014114_dt_ids,
2909e50d5fbSOleh Kravchenko },
2919e50d5fbSOleh Kravchenko };
2929e50d5fbSOleh Kravchenko
2939e50d5fbSOleh Kravchenko module_spi_driver(cr0014114_driver);
2949e50d5fbSOleh Kravchenko
2959e50d5fbSOleh Kravchenko MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>");
2969e50d5fbSOleh Kravchenko MODULE_DESCRIPTION("cr0014114 LED driver");
2979e50d5fbSOleh Kravchenko MODULE_LICENSE("GPL v2");
2989e50d5fbSOleh Kravchenko MODULE_ALIAS("spi:cr0014114");
299