/* * Copyright (c) 2018 Google LLC. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT apa_apa102 #include #include #include #include #include /* * Devicetree helper macros which gets the 'flags' cell from a 'cs_gpios' * property on the spi bus, or returns 0 if the property has no 'flags' cell. * * Hopefully these helpers will be removed once #25827 is resolved. */ #define HAS_FLAGS(spi_dev, spi_reg) \ DT_PHA_HAS_CELL_AT_IDX(spi_dev, cs_gpios, spi_reg, flags) #define INST_SPI_DEV_CS_GPIOS_HAS_FLAGS(node) \ HAS_FLAGS(DT_BUS(node), DT_REG_ADDR(node)) #define FLAGS_OR_ZERO(inst) \ COND_CODE_1( \ INST_SPI_DEV_CS_GPIOS_HAS_FLAGS(DT_DRV_INST(inst)), \ (DT_INST_SPI_DEV_CS_GPIOS_FLAGS(inst)), \ (0x0)) struct apa102_data { struct device *spi; struct spi_config cfg; #if DT_INST_SPI_DEV_HAS_CS_GPIOS(0) struct spi_cs_control cs_ctl; #endif /* DT_INST_SPI_DEV_HAS_CS_GPIOS(0) */ }; static int apa102_update(struct device *dev, void *buf, size_t size) { struct apa102_data *data = dev->driver_data; static const uint8_t zeros[] = {0, 0, 0, 0}; static const uint8_t ones[] = {0xFF, 0xFF, 0xFF, 0xFF}; const struct spi_buf tx_bufs[] = { { /* Start frame: at least 32 zeros */ .buf = (uint8_t *)zeros, .len = sizeof(zeros), }, { /* LED data itself */ .buf = buf, .len = size, }, { /* End frame: at least 32 ones to clock the * remaining bits to the LEDs at the end of * the strip. */ .buf = (uint8_t *)ones, .len = sizeof(ones), }, }; const struct spi_buf_set tx = { .buffers = tx_bufs, .count = ARRAY_SIZE(tx_bufs) }; return spi_write(data->spi, &data->cfg, &tx); } static int apa102_update_rgb(struct device *dev, struct led_rgb *pixels, size_t count) { uint8_t *p = (uint8_t *)pixels; size_t i; /* SOF (3 bits) followed by the 0 to 31 global dimming level */ uint8_t prefix = 0xE0 | 31; /* Rewrite to the on-wire format */ for (i = 0; i < count; i++) { uint8_t r = pixels[i].r; uint8_t g = pixels[i].g; uint8_t b = pixels[i].b; *p++ = prefix; *p++ = b; *p++ = g; *p++ = r; } BUILD_ASSERT(sizeof(struct led_rgb) == 4); return apa102_update(dev, pixels, sizeof(struct led_rgb) * count); } static int apa102_update_channels(struct device *dev, uint8_t *channels, size_t num_channels) { /* Not implemented */ return -EINVAL; } static int apa102_init(struct device *dev) { struct apa102_data *data = dev->driver_data; data->spi = device_get_binding(DT_INST_BUS_LABEL(0)); if (!data->spi) { return -ENODEV; } data->cfg.slave = DT_INST_REG_ADDR(0); data->cfg.frequency = DT_INST_PROP(0, spi_max_frequency); data->cfg.operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8); #if DT_INST_SPI_DEV_HAS_CS_GPIOS(0) data->cs_ctl.gpio_dev = device_get_binding(DT_INST_SPI_DEV_CS_GPIOS_LABEL(0)); if (!data->cs_ctl.gpio_dev) { return -ENODEV; } data->cs_ctl.gpio_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(0); data->cs_ctl.delay = 0; data->cfg.cs = &data->cs_ctl; gpio_pin_configure(data->cs_ctl.gpio_dev, data->cs_ctl.gpio_pin, GPIO_OUTPUT_INACTIVE | FLAGS_OR_ZERO(0)); #endif /* DT_INST_SPI_DEV_HAS_CS_GPIOS(0) */ return 0; } static struct apa102_data apa102_data_0; static const struct led_strip_driver_api apa102_api = { .update_rgb = apa102_update_rgb, .update_channels = apa102_update_channels, }; DEVICE_AND_API_INIT(apa102_0, DT_INST_LABEL(0), apa102_init, &apa102_data_0, NULL, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, &apa102_api);