| /* | 
 |  * Copyright (c) 2017 Linaro Limited | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #include <drivers/led_strip.h> | 
 |  | 
 | #include <errno.h> | 
 | #include <string.h> | 
 |  | 
 | #ifdef DT_INST_0_COLORWAY_LPD8806 | 
 | #define DT_INST_0_COLORWAY_LPD880X DT_INST_0_COLORWAY_LPD8806 | 
 | #define DT_INST_0_COLORWAY_LPD880X_BASE_ADDRESS DT_INST_0_COLORWAY_LPD8806_BASE_ADDRESS | 
 | #define DT_INST_0_COLORWAY_LPD880X_BUS_NAME DT_INST_0_COLORWAY_LPD8806_BUS_NAME | 
 | #define DT_INST_0_COLORWAY_LPD880X_LABEL DT_INST_0_COLORWAY_LPD8806_LABEL | 
 | #define DT_INST_0_COLORWAY_LPD880X_SPI_MAX_FREQUENCY DT_INST_0_COLORWAY_LPD8806_SPI_MAX_FREQUENCY | 
 | #else | 
 | #define DT_INST_0_COLORWAY_LPD880X DT_INST_0_COLORWAY_LPD8803 | 
 | #define DT_INST_0_COLORWAY_LPD880X_BASE_ADDRESS DT_INST_0_COLORWAY_LPD8803_BASE_ADDRESS | 
 | #define DT_INST_0_COLORWAY_LPD880X_BUS_NAME DT_INST_0_COLORWAY_LPD8803_BUS_NAME | 
 | #define DT_INST_0_COLORWAY_LPD880X_LABEL DT_INST_0_COLORWAY_LPD8803_LABEL | 
 | #define DT_INST_0_COLORWAY_LPD880X_SPI_MAX_FREQUENCY DT_INST_0_COLORWAY_LPD8803_SPI_MAX_FREQUENCY | 
 | #endif | 
 |  | 
 | #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL | 
 | #include <logging/log.h> | 
 | LOG_MODULE_REGISTER(lpd880x); | 
 |  | 
 | #include <zephyr.h> | 
 | #include <device.h> | 
 | #include <drivers/spi.h> | 
 | #include <sys/util.h> | 
 |  | 
 | /* | 
 |  * LPD880X SPI master configuration: | 
 |  * | 
 |  * - mode 0 (the default), 8 bit, MSB first, one-line SPI | 
 |  * - no shenanigans (no CS hold, release device lock, not an EEPROM) | 
 |  */ | 
 | #define LPD880X_SPI_OPERATION (SPI_OP_MODE_MASTER |	\ | 
 | 			       SPI_TRANSFER_MSB |	\ | 
 | 			       SPI_WORD_SET(8) |	\ | 
 | 			       SPI_LINES_SINGLE) | 
 |  | 
 | struct lpd880x_data { | 
 | 	struct device *spi; | 
 | 	struct spi_config config; | 
 | }; | 
 |  | 
 | static int lpd880x_update(struct device *dev, void *data, size_t size) | 
 | { | 
 | 	struct lpd880x_data *drv_data = dev->driver_data; | 
 | 	/* | 
 | 	 * Per the AdaFruit reverse engineering notes on the protocol, | 
 | 	 * a zero byte propagates through at most 32 LED driver ICs. | 
 | 	 * The LPD8803 is the worst case, at 3 output channels per IC. | 
 | 	 */ | 
 | 	u8_t reset_size = ceiling_fraction(ceiling_fraction(size, 3), 32); | 
 | 	u8_t reset_buf[reset_size]; | 
 | 	u8_t last = 0x00; | 
 | 	const struct spi_buf bufs[3] = { | 
 | 		{ | 
 | 			/* Prepares the strip to shift in new data values. */ | 
 | 			.buf = reset_buf, | 
 | 			.len = reset_size | 
 | 		}, | 
 | 		{ | 
 | 			/* Displays the serialized pixel data. */ | 
 | 			.buf = data, | 
 | 			.len = size | 
 | 		}, | 
 | 		{ | 
 | 			/* Ensures the last byte of pixel data is displayed. */ | 
 | 			.buf = &last, | 
 | 			.len = sizeof(last) | 
 | 		} | 
 |  | 
 | 	}; | 
 | 	const struct spi_buf_set tx = { | 
 | 		.buffers = bufs, | 
 | 		.count = 3 | 
 | 	}; | 
 | 	size_t rc; | 
 |  | 
 | 	(void)memset(reset_buf, 0x00, reset_size); | 
 |  | 
 | 	rc = spi_write(drv_data->spi, &drv_data->config, &tx); | 
 | 	if (rc) { | 
 | 		LOG_ERR("can't update strip: %d", rc); | 
 | 	} | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int lpd880x_strip_update_rgb(struct device *dev, | 
 | 				    struct led_rgb *pixels, | 
 | 				    size_t num_pixels) | 
 | { | 
 | 	u8_t *px = (u8_t *)pixels; | 
 | 	u8_t r, g, b; | 
 | 	size_t i; | 
 |  | 
 | 	/* | 
 | 	 * Overwrite a prefix of the pixels array with its on-wire | 
 | 	 * representation, eliminating padding/scratch garbage, if any. | 
 | 	 */ | 
 | 	for (i = 0; i < num_pixels; i++) { | 
 | 		r = 0x80 | (pixels[i].r >> 1); | 
 | 		g = 0x80 | (pixels[i].g >> 1); | 
 | 		b = 0x80 | (pixels[i].b >> 1); | 
 |  | 
 | 		/* | 
 | 		 * GRB is the ordering used by commonly available | 
 | 		 * LPD880x strips. | 
 | 		 */ | 
 | 		*px++ = g; | 
 | 		*px++ = r; | 
 | 		*px++ = b; | 
 | 	} | 
 |  | 
 | 	return lpd880x_update(dev, pixels, 3 * num_pixels); | 
 | } | 
 |  | 
 | static int lpd880x_strip_update_channels(struct device *dev, u8_t *channels, | 
 | 					 size_t num_channels) | 
 | { | 
 | 	size_t i; | 
 |  | 
 | 	for (i = 0; i < num_channels; i++) { | 
 | 		channels[i] = 0x80 | (channels[i] >> 1); | 
 | 	} | 
 |  | 
 | 	return lpd880x_update(dev, channels, num_channels); | 
 | } | 
 |  | 
 | static int lpd880x_strip_init(struct device *dev) | 
 | { | 
 | 	struct lpd880x_data *data = dev->driver_data; | 
 | 	struct spi_config *config = &data->config; | 
 |  | 
 | 	data->spi = device_get_binding(DT_INST_0_COLORWAY_LPD880X_BUS_NAME); | 
 | 	if (!data->spi) { | 
 | 		LOG_ERR("SPI device %s not found", | 
 | 			    DT_INST_0_COLORWAY_LPD880X_BUS_NAME); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	config->frequency = DT_INST_0_COLORWAY_LPD880X_SPI_MAX_FREQUENCY; | 
 | 	config->operation = LPD880X_SPI_OPERATION; | 
 | 	config->slave = DT_INST_0_COLORWAY_LPD880X_BASE_ADDRESS; /* MOSI/CLK only; CS is not supported. */ | 
 | 	config->cs = NULL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct lpd880x_data lpd880x_strip_data; | 
 |  | 
 | static const struct led_strip_driver_api lpd880x_strip_api = { | 
 | 	.update_rgb = lpd880x_strip_update_rgb, | 
 | 	.update_channels = lpd880x_strip_update_channels, | 
 | }; | 
 |  | 
 | DEVICE_AND_API_INIT(lpd880x_strip, DT_INST_0_COLORWAY_LPD880X_LABEL, | 
 | 		    lpd880x_strip_init, &lpd880x_strip_data, | 
 | 		    NULL, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, | 
 | 		    &lpd880x_strip_api); |