diff options
author | Álvaro Fernández Rojas <noltari@gmail.com> | 2023-03-30 23:14:13 +0200 |
---|---|---|
committer | Álvaro Fernández Rojas <noltari@gmail.com> | 2023-04-09 10:33:13 +0200 |
commit | aefeb34223022c65ea0e7eef49c6c39250afd4ea (patch) | |
tree | 35a8a86da05499acbcdbbacd9729fb744732b470 /target/linux/bmips/files | |
parent | d11a7c4d95a5ecbd2b12750be63349a3d0da2d53 (diff) | |
download | upstream-aefeb34223022c65ea0e7eef49c6c39250afd4ea.tar.gz upstream-aefeb34223022c65ea0e7eef49c6c39250afd4ea.tar.bz2 upstream-aefeb34223022c65ea0e7eef49c6c39250afd4ea.zip |
bmips: add support for Sercomm SHG2500
Sercomm SHG2500 is a BCM63168 with 128M of RAM, 256M of NAND, an external
BCM53124S switch for the LAN ports and internal/external Broadcom wifi.
LEDs are connected to an external MSP430G2513 MCU controlled via SPI.
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Diffstat (limited to 'target/linux/bmips/files')
-rw-r--r-- | target/linux/bmips/files/drivers/leds/leds-sercomm-msp430.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/target/linux/bmips/files/drivers/leds/leds-sercomm-msp430.c b/target/linux/bmips/files/drivers/leds/leds-sercomm-msp430.c new file mode 100644 index 0000000000..5409142989 --- /dev/null +++ b/target/linux/bmips/files/drivers/leds/leds-sercomm-msp430.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Sercomm MSP430G2513 LEDs. + * + * Copyright 2023 Álvaro Fernández Rojas <noltari@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include "leds.h" + +/* + * MSP430G2513 SPI protocol description: + * +----+----+----+----+----+----+ + * | b1 | b2 | b3 | b4 | b5 | b6 | + * +----+----+----+----+----+----+ + * 6 bytes TX & RX per transaction. + * + * LEDs: + * MSP430G2513 can control up to 9 LEDs. + * b1: LED ID [1,9] + * b2: LED function + * b3-b6: LED function parameters + * + * LED functions: + * [0] Off + * [1] On + * [2] Flash + * - b4: delay (x 6ms) + * - b5: repeat (0 = infinite) + * [3] Pulse + * - b3: delay (x 6ms) + * - b4: blink while pulsing? (unknown) + * - b5: repeat (0 = infinite) + * [4] Pulse On + * - b3: delay (x 6ms) + * - b4: blink while pulsing? (unknown) + * - b5: repeat (0 = infinite) + * [5] Pulse Off + * - b3: delay (x 6ms) + * - b4: blink while pulsing? (unknown) + * - b5: repeat (0 = infinite) + * [6] Level + * - b3: brightness [0,4] + * + * MCU Commands (b1 = 0x55): + * [0x0a] FW upgrade data + * - b3: Data size (usually 0x40), which is appended to TX & RX. + * [0x31] Get MCU version? (unknown) + * [0x68] Get MCU work mode + * [0xa5] Start FW upgrade + * [0xf0] End FW upgrade + */ + +#define MSP430_CMD_BYTES 6 +#define MSP430_CMD_MCU 0x55 +#define MSP430_MCU_WM 0x68 + +#define MSP430_LED_MIN_ID 1 +#define MSP430_LED_MAX_ID 9 + +#define MSP430_LED_OFF 0 +#define MSP430_LED_ON 1 +#define MSP430_LED_FLASH 2 +#define MSP430_LED_PULSE 3 +#define MSP430_LED_PULSE_ON 4 +#define MSP430_LED_PULSE_OFF 5 +#define MSP430_LED_LEVEL 6 + +#define MSP430_LED_BLINK_DEF 500 +#define MSP430_LED_BLINK_MASK 0xff +#define MSP430_LED_BLINK_MS 6 +#define MSP430_LED_BLINK_MAX (MSP430_LED_BLINK_MS * \ + MSP430_LED_BLINK_MASK) + +#define MSP430_LED_BRIGHTNESS_MAX 5 +#define MSP430_LED_REPEAT_MAX 0xff + +/** + * struct msp430_led - state container for Sercomm MSP430 based LEDs + * @cdev: LED class device for this LED + * @spi: spi resource + * @id: LED ID + */ +struct msp430_led { + struct led_classdev cdev; + struct spi_device *spi; + u8 id; +}; + +static inline int msp430_cmd(struct spi_device *spi, u8 tx[MSP430_CMD_BYTES], + u8 rx[MSP430_CMD_BYTES]) +{ + struct device *dev = &spi->dev; + int rc; + + memset(rx, 0, MSP430_CMD_BYTES); + + rc = spi_write_then_read(spi, tx, MSP430_CMD_BYTES, + rx, MSP430_CMD_BYTES); + if (rc) + dev_err(dev, "spi error\n"); + + dev_dbg(dev, "msp430_cmd: [%02x %02x %02x %02x %02x %02x]" + " -> [%02x %02x %02x %02x %02x %02x]", + tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5]); + + return rc; +} + +static unsigned long msp430_blink_delay(unsigned long delay) +{ + unsigned long msp430_delay; + + msp430_delay = delay + MSP430_LED_BLINK_MS / 2; + msp430_delay = msp430_delay / MSP430_LED_BLINK_MS; + if (msp430_delay == 0) + msp430_delay = 1; + + return msp430_delay; +} + +static int msp430_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct msp430_led *led = + container_of(led_cdev, struct msp430_led, cdev); + u8 tx[MSP430_CMD_BYTES] = {led->id, MSP430_LED_FLASH, 0, 0, 0, 0}; + u8 rx[MSP430_CMD_BYTES]; + unsigned long delay; + + if (!*delay_on) + *delay_on = MSP430_LED_BLINK_DEF; + if (!*delay_off) + *delay_off = MSP430_LED_BLINK_DEF; + + delay = msp430_blink_delay(*delay_on); + if (delay != msp430_blink_delay(*delay_off)) { + dev_dbg(led_cdev->dev, + "fallback to soft blinking (delay_on != delay_off)\n"); + return -EINVAL; + } + + if (delay > MSP430_LED_BLINK_MASK) { + dev_dbg(led_cdev->dev, + "fallback to soft blinking (delay > %ums)\n", + MSP430_LED_BLINK_MAX); + return -EINVAL; + } + + tx[3] = delay; + + return msp430_cmd(led->spi, tx, rx); +} + +static int msp430_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct msp430_led *led = + container_of(led_cdev, struct msp430_led, cdev); + u8 tx[MSP430_CMD_BYTES] = {led->id, 0, 0, 0, 0, 0}; + u8 rx[MSP430_CMD_BYTES]; + u8 val = (u8) brightness; + + switch (val) + { + case LED_OFF: + tx[1] = MSP430_LED_OFF; + break; + case MSP430_LED_BRIGHTNESS_MAX: + tx[1] = MSP430_LED_ON; + break; + default: + tx[1] = MSP430_LED_LEVEL; + tx[2] = val - 1; + break; + } + + return msp430_cmd(led->spi, tx, rx); +} + +static int msp430_pattern_clear(struct led_classdev *ldev) +{ + msp430_brightness_set(ldev, LED_OFF); + + return 0; +} + +static int msp430_pattern_set(struct led_classdev *led_cdev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct msp430_led *led = + container_of(led_cdev, struct msp430_led, cdev); + u8 tx[MSP430_CMD_BYTES] = {led->id, 0, 0, 0, 0, 0}; + u8 rx[MSP430_CMD_BYTES]; + unsigned long delay0; + unsigned long delay1; + int rc; + + if (len != 2 || + repeat > MSP430_LED_REPEAT_MAX || + pattern[0].delta_t > MSP430_LED_BLINK_MAX || + pattern[1].delta_t > MSP430_LED_BLINK_MAX) + return -EINVAL; + + delay0 = msp430_blink_delay(pattern[0].delta_t); + delay1 = msp430_blink_delay(pattern[1].delta_t); + + /* Pulse: <off> <delay> <max> <delay> */ + if (delay0 == delay1 && + pattern[0].brightness == LED_OFF && + pattern[1].brightness == MSP430_LED_BRIGHTNESS_MAX) + { + tx[1] = MSP430_LED_PULSE; + tx[2] = delay0; + tx[4] = (u8) repeat; + } + + /* Pulse On: <off> <delay> <max> <0ms> */ + if (pattern[0].delta_t != 0 && + pattern[1].delta_t == 0 && + pattern[0].brightness == LED_OFF && + pattern[1].brightness == MSP430_LED_BRIGHTNESS_MAX) { + tx[1] = MSP430_LED_PULSE_ON; + tx[2] = delay0; + tx[4] = (u8) repeat; + } + + /* Pulse Off: <max> <delay> <off> <0ms> */ + if (pattern[0].delta_t != 0 && + pattern[1].delta_t == 0 && + pattern[0].brightness == MSP430_LED_BRIGHTNESS_MAX && + pattern[1].brightness == LED_OFF) { + tx[1] = MSP430_LED_PULSE_OFF; + tx[2] = delay0; + tx[4] = (u8) repeat; + } + + if (!tx[1]) + return -EINVAL; + + rc = msp430_cmd(led->spi, tx, rx); + if (rc) + return rc; + + return 0; +} + +static int msp430_led(struct spi_device *spi, struct device_node *nc, u8 id) +{ + struct device *dev = &spi->dev; + struct led_init_data init_data = {}; + struct msp430_led *led; + enum led_default_state state; + int rc; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->id = id; + led->spi = spi; + + init_data.fwnode = of_fwnode_handle(nc); + + state = led_init_default_state_get(init_data.fwnode); + switch (state) { + case LEDS_DEFSTATE_ON: + led->cdev.brightness = MSP430_LED_BRIGHTNESS_MAX; + break; + default: + led->cdev.brightness = LED_OFF; + break; + } + + msp430_brightness_set(&led->cdev, led->cdev.brightness); + + led->cdev.blink_set = msp430_blink_set; + led->cdev.brightness_set_blocking = msp430_brightness_set; + led->cdev.max_brightness = MSP430_LED_BRIGHTNESS_MAX; + led->cdev.pattern_clear = msp430_pattern_clear; + led->cdev.pattern_set = msp430_pattern_set; + + rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); + if (rc < 0) + return rc; + + dev_dbg(dev, "registered LED %s\n", led->cdev.name); + + return 0; +} + +static inline int msp430_check_workmode(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + u8 tx[MSP430_CMD_BYTES] = {MSP430_CMD_MCU, MSP430_MCU_WM, 0, 0, 0, 0}; + u8 rx[MSP430_CMD_BYTES]; + int rc; + + rc = msp430_cmd(spi, tx, rx); + if (rc) + return rc; + + if ((rx[3] == 0xA5 && rx[4] == 'Z') || + (rx[4] == 0xA5 && rx[5] == 'Z') || + (rx[4] == '\b' && rx[5] == '\n')) { + dev_err(dev, "invalid workmode: " + "[%02x %02x %02x %02x %02x %02x]\n", + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5]); + return -EINVAL; + } + + return 0; +} + +static int msp430_leds_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct device_node *np = dev_of_node(dev); + struct device_node *child; + int rc; + + rc = msp430_check_workmode(spi); + if (rc) + return rc; + + for_each_available_child_of_node(np, child) { + u32 reg; + + if (of_property_read_u32(child, "reg", ®)) + continue; + + if (reg < MSP430_LED_MIN_ID || reg > MSP430_LED_MAX_ID) { + dev_err(dev, "invalid LED (%u) [%d, %d]\n", reg, + MSP430_LED_MIN_ID, MSP430_LED_MAX_ID); + continue; + } + + rc = msp430_led(spi, child, reg); + if (rc < 0) { + of_node_put(child); + return rc; + } + } + + return 0; +} + +static const struct of_device_id msp430_leds_of_match[] = { + { .compatible = "sercomm,msp430-leds", }, + { }, +}; +MODULE_DEVICE_TABLE(of, msp430_leds_of_match); + +static const struct spi_device_id msp430_leds_id_table[] = { + { "msp430-leds", 0 }, + { } +}; + +static struct spi_driver msp430_leds_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = msp430_leds_of_match, + }, + .id_table = msp430_leds_id_table, + .probe = msp430_leds_probe, +}; + +module_spi_driver(msp430_leds_driver); + +MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); +MODULE_DESCRIPTION("LED driver for Sercomm MSP430 controllers"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds-sercomm-msp430"); |