From 8b9fd1507ef4d9b063fc434269b171757f431360 Mon Sep 17 00:00:00 2001 From: Paolo Sabatino Date: Sat, 5 Oct 2024 18:17:22 +0200 Subject: [PATCH] [rockchip64] add tm16xx led driver for display panels --- config/kernel/linux-rockchip64-current.config | 10 +- config/kernel/linux-rockchip64-edge.config | 12 +- packages/bsp/rk3318/rk3318-config | 5 +- .../general-driver-tm16xx-led-driver.patch | 1222 +++++++++++++++++ .../archive/rockchip64-6.11/overlay/Makefile | 1 + .../rockchip-rk3318-box-led-conf1.dtso | 15 +- .../rockchip-rk3318-box-led-conf2.dtso | 72 +- .../rockchip-rk3318-box-led-conf5.dtso | 104 ++ .../general-driver-tm16xx-led-driver.patch | 1221 ++++++++++++++++ .../archive/rockchip64-6.6/overlay/Makefile | 1 + .../overlay/rockchip-rk3318-box-led-conf1.dts | 15 +- .../overlay/rockchip-rk3318-box-led-conf2.dts | 72 +- .../overlay/rockchip-rk3318-box-led-conf5.dts | 104 ++ 13 files changed, 2798 insertions(+), 56 deletions(-) create mode 100644 patch/kernel/archive/rockchip64-6.11/general-driver-tm16xx-led-driver.patch create mode 100644 patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf5.dtso create mode 100644 patch/kernel/archive/rockchip64-6.6/general-driver-tm16xx-led-driver.patch create mode 100644 patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf5.dts diff --git a/config/kernel/linux-rockchip64-current.config b/config/kernel/linux-rockchip64-current.config index af4eebd02..49f06521e 100644 --- a/config/kernel/linux-rockchip64-current.config +++ b/config/kernel/linux-rockchip64-current.config @@ -5910,7 +5910,15 @@ CONFIG_DVB_DUMMY_FE=m CONFIG_APERTURE_HELPERS=y CONFIG_VIDEO_CMDLINE=y CONFIG_VIDEO_NOMODESET=y -# CONFIG_AUXDISPLAY is not set +CONFIG_AUXDISPLAY=y +# CONFIG_HD44780 is not set +# CONFIG_IMG_ASCII_LCD is not set +# CONFIG_HT16K33 is not set +CONFIG_TM16XX=m +# CONFIG_LCD2S is not set +CONFIG_CHARLCD_BL_OFF=y +# CONFIG_CHARLCD_BL_ON is not set +# CONFIG_CHARLCD_BL_FLASH is not set CONFIG_DRM=y CONFIG_DRM_MIPI_DBI=m CONFIG_DRM_MIPI_DSI=y diff --git a/config/kernel/linux-rockchip64-edge.config b/config/kernel/linux-rockchip64-edge.config index 6e03d5b44..81743a7f5 100644 --- a/config/kernel/linux-rockchip64-edge.config +++ b/config/kernel/linux-rockchip64-edge.config @@ -6026,7 +6026,17 @@ CONFIG_DVB_DUMMY_FE=m # CONFIG_APERTURE_HELPERS=y CONFIG_VIDEO=y -# CONFIG_AUXDISPLAY is not set +CONFIG_AUXDISPLAY=y +# CONFIG_HD44780 is not set +CONFIG_TM16XX=m +# CONFIG_LCD2S is not set +CONFIG_CHARLCD_BL_OFF=y +# CONFIG_CHARLCD_BL_ON is not set +# CONFIG_CHARLCD_BL_FLASH is not set +# CONFIG_IMG_ASCII_LCD is not set +# CONFIG_HT16K33 is not set +# CONFIG_MAX6959 is not set +# CONFIG_SEG_LED_GPIO is not set CONFIG_DRM=y CONFIG_DRM_MIPI_DBI=m CONFIG_DRM_MIPI_DSI=y diff --git a/packages/bsp/rk3318/rk3318-config b/packages/bsp/rk3318/rk3318-config index e52530704..cdf8a01f4 100755 --- a/packages/bsp/rk3318/rk3318-config +++ b/packages/bsp/rk3318/rk3318-config @@ -70,12 +70,13 @@ WIFI_CHIPS+=(["02d0:4330"]="") DT_EMMC_OVERLAYS+=(["rk3318-box-emmc-ddr"]="enable eMMC DDR Mode") DT_EMMC_OVERLAYS+=(["rk3318-box-emmc-hs200"]="enable eMMC HS200 Mode") -DT_LED_OVERLAYS+=(["rk3318-box-led-conf1"]="Generic (also YX_RK3328, RK3318_V1.x)") +DT_LED_OVERLAYS+=(["rk3318-box-led-conf1"]="Generic (like RK3318_V1.x)") DT_LED_OVERLAYS+=(["rk3318-box-led-conf4"]="Generic + wifi on sdmmc-ext") DT_LED_OVERLAYS+=(["rk3318-box-led-conf2"]="X88_PRO_B boards") DT_LED_OVERLAYS+=(["rk3318-box-led-conf3"]="MXQ-RK3328-D4 boards (w/ RK805)") +DT_LED_OVERLAYS+=(["rk3318-box-led-conf5"]="YX_RK3318 boards") -DT_LED_OVERLAYS_ORDER=("rk3318-box-led-conf1" "rk3318-box-led-conf4" "rk3318-box-led-conf2" "rk3318-box-led-conf3") +DT_LED_OVERLAYS_ORDER=("rk3318-box-led-conf1" "rk3318-box-led-conf4" "rk3318-box-led-conf2" "rk3318-box-led-conf3" "rk3318-box-led-conf5") DT_CPU_OVERLAYS+=(["rk3318-box-cpu-hs"]="RK3318 or RK3328") diff --git a/patch/kernel/archive/rockchip64-6.11/general-driver-tm16xx-led-driver.patch b/patch/kernel/archive/rockchip64-6.11/general-driver-tm16xx-led-driver.patch new file mode 100644 index 000000000..9d226168d --- /dev/null +++ b/patch/kernel/archive/rockchip64-6.11/general-driver-tm16xx-led-driver.patch @@ -0,0 +1,1222 @@ +From b305f05351984645293a9b7b2047779f6d8fbdd2 Mon Sep 17 00:00:00 2001 +From: Paolo Sabatino +Date: Sat, 5 Oct 2024 16:07:14 +0200 +Subject: [PATCH] Add tm16xx led auxiliary display driver + +--- + drivers/auxdisplay/Kconfig | 10 + + drivers/auxdisplay/Makefile | 1 + + drivers/auxdisplay/tm16xx.c | 1167 +++++++++++++++++++++++++++++++++++ + 3 files changed, 1178 insertions(+) + create mode 100644 drivers/auxdisplay/tm16xx.c + +diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig +index 64012cda4d12..3fe0088b7f1f 100644 +--- a/drivers/auxdisplay/Kconfig ++++ b/drivers/auxdisplay/Kconfig +@@ -183,6 +183,16 @@ config HT16K33 + Say yes here to add support for Holtek HT16K33, RAM mapping 16*8 + LED controller driver with keyscan. + ++config TM16XX ++ tristate "TM16xx/FD6xx LED controller driver and compatibles" ++ depends on I2C ++ select NEW_LEDS ++ select LEDS_CLASS ++ help ++ Say yes here to add support for Titanmicro TM1628, TM1650, ++ FudaHisi FD628, FD650, FD655, FD6551, RAM mapping 4*8/5*8 ++ LED controller drivers and compatibles ++ + config LCD2S + tristate "lcd2s 20x4 character display over I2C console" + depends on I2C +diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile +index f5c13ed1cd4f..066f452b1e26 100644 +--- a/drivers/auxdisplay/Makefile ++++ b/drivers/auxdisplay/Makefile +@@ -9,6 +9,7 @@ obj-$(CONFIG_CHARLCD) += charlcd.o + obj-$(CONFIG_HD44780_COMMON) += hd44780_common.o + obj-$(CONFIG_HD44780) += hd44780.o + obj-$(CONFIG_HT16K33) += ht16k33.o ++obj-$(CONFIG_TM16XX) += tm16xx.o + obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o + obj-$(CONFIG_KS0108) += ks0108.o + obj-$(CONFIG_LCD2S) += lcd2s.o +diff --git a/drivers/auxdisplay/tm16xx.c b/drivers/auxdisplay/tm16xx.c +new file mode 100644 +index 000000000000..d938b0166e74 +--- /dev/null ++++ b/drivers/auxdisplay/tm16xx.c +@@ -0,0 +1,1167 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Auxiliary Display Driver for TM16XX and compatible LED controllers ++ * ++ * Copyright (C) 2024 Jean-François Lessard ++ * ++ * This driver supports various LED controller chips, including TM16XX family, ++ * FD6XX family, PT6964, and HBS658. It provides support for both I2C and SPI interfaces. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TM16XX_DRIVER_NAME "tm16xx" ++#define TM16XX_DEVICE_NAME "display" ++ ++/* Forward declarations */ ++struct tm16xx_display; ++ ++/** ++ * struct tm16xx_controller - Controller-specific operations ++ * @max_brightness: Maximum brightness level supported by the controller ++ * @init: Initialize the controller ++ * @brightness: Set brightness level ++ * @data: Write display data ++ * ++ * This structure holds function pointers for controller-specific operations. ++ */ ++struct tm16xx_controller { ++ u8 max_brightness; ++ int (*init)(struct tm16xx_display *display, u8 **cmd); ++ int (*brightness)(struct tm16xx_display *display, u8 **cmd); ++ int (*data)(struct tm16xx_display *display, u8 **cmd, int data_index); ++}; ++ ++/** ++ * struct tm16xx_led - LED information ++ * @cdev: LED class device ++ * @grid: Grid index of the LED ++ * @segment: Segment index of the LED ++ */ ++struct tm16xx_led { ++ struct led_classdev cdev; ++ u8 grid; ++ u8 segment; ++}; ++ ++/** ++ * struct tm16xx_digit - Digit information ++ * @grid: Grid index of the digit ++ * @value: Current value of the digit ++ */ ++struct tm16xx_digit { ++ u8 grid; ++ char value; ++}; ++ ++/** ++ * struct tm16xx_display - Main driver structure ++ * @dev: Pointer to device structure ++ * @controller: Pointer to controller-specific operations ++ * @client: Union of I2C and SPI client structures ++ * @main_led: LED class device for the entire display ++ * @leds: Array of individual LEDs ++ * @num_leds: Number of LEDs ++ * @digits: Array of digits ++ * @num_digits: Number of digits ++ * @segment_mapping: Segment mapping array ++ * @num_segments: Number of segments ++ * @display_data: Display data buffer ++ * @display_data_len: Length of display data buffer ++ * @lock: Mutex for concurrent access protection ++ * @flush_brightness: Work structure for brightness update ++ * @flush_display: Work structure for display update ++ * @client_write: Function pointer for client write operation ++ */ ++struct tm16xx_display { ++ struct device *dev; ++ const struct tm16xx_controller *controller; ++ union { ++ struct i2c_client *i2c; ++ struct spi_device *spi; ++ } client; ++ struct led_classdev main_led; ++ struct tm16xx_led *leds; ++ int num_leds; ++ struct tm16xx_digit *digits; ++ int num_digits; ++ u8 *segment_mapping; ++ int num_segments; ++ u8 *display_data; ++ size_t display_data_len; ++ struct mutex lock; ++ struct work_struct flush_brightness; ++ struct work_struct flush_display; ++ int (*client_write)(struct tm16xx_display *display, u8 *data, size_t len); ++}; ++ ++/* Controller-specific functions */ ++static int tm1628_cmd_init(struct tm16xx_display *display, u8 **cmd) ++{ ++ // 01b mode is 5 grids with minimum of 7 segments ++ // tm1618 : 5 digits, 7 segments ++ // tm1620 : 5 digits, 9 segments ++ // tm1628 : 5 digits, 12 segments ++ static const u8 MODE_CMD = 0 << 7 | 0 << 6, MODE = 0 << 1 | 1 << 0; ++ static const u8 DATA_CMD = 0 << 7 | 1 << 6, DATA_ADDR_MODE = 0 << 2, DATA_WRITE_MODE = 0 << 1 | 0 << 0; ++ ++ static u8 cmds[] = { ++ MODE_CMD | MODE, ++ DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE, ++ }; ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1628_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[1]; ++ static const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = CTRL_CMD | ((i && 1) * (((i-1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1618_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static const u8 BYTE1_MASK = 0x1F, BYTE1_RSHIFT = 0; ++ static const u8 BYTE2_MASK = ~BYTE1_MASK, BYTE2_RSHIFT = 5-3; ++ static u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = (display->display_data[i] & BYTE1_MASK) >> BYTE1_RSHIFT; ++ cmds[2] = (display->display_data[i] & BYTE2_MASK) >> BYTE2_RSHIFT; ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1628_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ cmds[2] = 0; // SEG 9 to 14 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1650_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 4, SEG7_MODE = 1 << 3; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * ((i & BR_MASK) << BR_SHIFT | SEG7_MODE | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1650_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 BASE_ADDR = 0x68; ++ static u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd655_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 3, BR_SHIFT = 5; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * (((i%3) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd655_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 BASE_ADDR = 0x66; ++ static u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd6551_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 1; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * ((~(i-1) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++void hbs658_msb_to_lsb(u8 *array, size_t length) { ++ for (size_t i = 0; i < length; i++) { ++ array[i] = (array[i] << 4) | (array[i] >> 4); ++ } ++} ++ ++static int hbs658_cmd_init(struct tm16xx_display *display, u8 **cmd) { ++ static const u8 DATA_CMD = 0 << 7 | 1 << 6, DATA_ADDR_MODE = 0 << 2, DATA_WRITE_MODE = 0 << 1 | 0 << 0; ++ ++ static u8 cmds[] = { ++ DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE, ++ }; ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int hbs658_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[1]; ++ static const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = CTRL_CMD | ((i && 1) * (((i-1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int hbs658_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static u8 cmds[2]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = display->display_data[i]; ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static const struct tm16xx_controller tm1618_controller = { ++ .max_brightness = 8, ++ .init = tm1628_cmd_init, ++ .brightness = tm1628_cmd_brightness, ++ .data = tm1618_cmd_data, ++}; ++ ++static const struct tm16xx_controller tm1628_controller = { ++ .max_brightness = 8, ++ .init = tm1628_cmd_init, ++ .brightness = tm1628_cmd_brightness, ++ .data = tm1628_cmd_data, ++}; ++ ++static const struct tm16xx_controller tm1650_controller = { ++ .max_brightness = 8, ++ .init = NULL, ++ .brightness = tm1650_cmd_brightness, ++ .data = tm1650_cmd_data, ++}; ++ ++static const struct tm16xx_controller fd655_controller = { ++ .max_brightness = 3, ++ .init = NULL, ++ .brightness = fd655_cmd_brightness, ++ .data = fd655_cmd_data, ++}; ++ ++static const struct tm16xx_controller fd6551_controller = { ++ .max_brightness = 8, ++ .init = NULL, ++ .brightness = fd6551_cmd_brightness, ++ .data = fd655_cmd_data, ++}; ++ ++static const struct tm16xx_controller hbs658_controller = { ++ .max_brightness = 8, ++ .init = hbs658_cmd_init, ++ .brightness = hbs658_cmd_brightness, ++ .data = hbs658_cmd_data, ++}; ++ ++static int parse_int_array(const char *buf, int **array) ++{ ++ int *values, value, count = 0, len; ++ const char *ptr = buf; ++ ++ while (1 == sscanf(ptr, "%d %n", &value, &len)) { ++ count++; ++ ptr += len; ++ } ++ ++ if (count == 0) { ++ *array = NULL; ++ return 0; ++ } ++ ++ values = kmalloc(count * sizeof(*values), GFP_KERNEL); ++ if (!values) ++ return -ENOMEM; ++ ++ ptr = buf; ++ count = 0; ++ while (1 == sscanf(ptr, "%d %n", &value, &len)) { ++ values[count++] = value; ++ ptr += len; ++ } ++ ++ *array = values; ++ return count; ++} ++ ++static SEG7_CONVERSION_MAP(map_seg7, MAP_ASCII7SEG_ALPHANUM); ++ ++/** ++ * tm16xx_ascii_to_segments - Convert ASCII character to segment pattern ++ * @display: Pointer to tm16xx_display structure ++ * @c: ASCII character to convert ++ * ++ * Return: Segment pattern for the given ASCII character ++ */ ++static u8 tm16xx_ascii_to_segments(struct tm16xx_display *display, char c) ++{ ++ u8 standard_segments, mapped_segments = 0; ++ int i; ++ ++ standard_segments = map_to_seg7(&map_seg7, c); ++ ++ for (i = 0; i < 7; i++) { ++ if (standard_segments & BIT(i)) ++ mapped_segments |= BIT(display->segment_mapping[i]); ++ } ++ ++ return mapped_segments; ++} ++ ++/** ++ * tm16xx_i2c_write - Write data to I2C client ++ * @display: Pointer to tm16xx_display structure ++ * @data: Data to write ++ * @len: Length of data ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static int tm16xx_i2c_write(struct tm16xx_display *display, u8 *data, size_t len) ++{ ++ dev_dbg(display->dev, "i2c_write %*ph", (char)len, data); ++ ++ struct i2c_msg msg = { ++ .addr = data[0] >> 1, ++ .flags = 0, ++ .len = len - 1, ++ .buf = &data[1], ++ }; ++ int ret; ++ ++ ret = i2c_transfer(display->client.i2c->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ ++ return (ret == 1) ? len : -EIO; ++} ++ ++/** ++ * tm16xx_spi_write - Write data to SPI client ++ * @display: Pointer to tm16xx_display structure ++ * @data: Data to write ++ * @len: Length of data ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static int tm16xx_spi_write(struct tm16xx_display *display, u8 *data, size_t len) ++{ ++ dev_dbg(display->dev, "spi_write %*ph", (char)len, data); ++ ++ struct spi_device *spi = display->client.spi; ++ ++ return spi_write(spi, data, len); ++} ++ ++/** ++ * tm16xx_display_flush_brightness - Work function to update brightness ++ * @work: Pointer to work_struct ++ */ ++static void tm16xx_display_flush_brightness(struct work_struct *work) ++{ ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_brightness); ++ u8 *cmd; ++ int len = -1, ret; ++ ++ if (display->controller->brightness) { ++ len = display->controller->brightness(display, &cmd); ++ } ++ ++ if (len > 0) { ++ mutex_lock(&display->lock); ++ ret = display->client_write(display, cmd, len); ++ mutex_unlock(&display->lock); ++ if (ret < 0) ++ dev_err(display->dev, "Failed to set brightness: %d\n", ret); ++ } ++} ++ ++/** ++ * tm16xx_display_flush_data - Work function to update display data ++ * @work: Pointer to work_struct ++ */ ++static void tm16xx_display_flush_data(struct work_struct *work) ++{ ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_display); ++ u8 *cmd; ++ int i, len = -1, ret; ++ ++ mutex_lock(&display->lock); ++ ++ for (i = 0; i < display->display_data_len; i++) { ++ if (display->controller->data) { ++ len = display->controller->data(display, &cmd, i); ++ } ++ ++ if (len > 0) { ++ ret = display->client_write(display, cmd, len); ++ if (ret < 0) { ++ dev_err(display->dev, "Failed to write display data: %d\n", ret); ++ break; ++ } ++ } ++ } ++ ++ mutex_unlock(&display->lock); ++} ++ ++/** ++ * tm16xx_display_init - Initialize the display ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_display_init(struct tm16xx_display *display) ++{ ++ u8 *cmd; ++ int len = -1, ret; ++ ++ if (display->controller->init) { ++ len = display->controller->init(display, &cmd); ++ } ++ ++ if (len > 0) { ++ mutex_lock(&display->lock); ++ ret = display->client_write(display, cmd, len); ++ mutex_unlock(&display->lock); ++ if (ret < 0) ++ return ret; ++ } ++ ++ schedule_work(&display->flush_brightness); ++ flush_work(&display->flush_brightness); ++ ++ memset(display->display_data, 0xFF, display->display_data_len); ++ schedule_work(&display->flush_display); ++ flush_work(&display->flush_display); ++ memset(display->display_data, 0x00, display->display_data_len); ++ ++ return 0; ++} ++ ++/** ++ * tm16xx_display_remove - Remove the display ++ * @display: Pointer to tm16xx_display structure ++ */ ++static void tm16xx_display_remove(struct tm16xx_display *display) ++{ ++ memset(display->display_data, 0x00, display->display_data_len); ++ schedule_work(&display->flush_display); ++ flush_work(&display->flush_display); ++ ++ display->main_led.brightness = LED_OFF; ++ schedule_work(&display->flush_brightness); ++ flush_work(&display->flush_brightness); ++ ++ dev_info(display->dev, "Display turned off\n"); ++} ++ ++/** ++ * tm16xx_brightness_set - Set brightness of the display ++ * @led_cdev: Pointer to led_classdev ++ * @brightness: Brightness value to set ++ */ ++static void tm16xx_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ led_cdev->brightness = brightness; ++ schedule_work(&display->flush_brightness); ++} ++ ++/** ++ * tm16xx_led_set - Set state of an individual LED ++ * @led_cdev: Pointer to led_classdev ++ * @value: Value to set (on/off) ++ */ ++static void tm16xx_led_set(struct led_classdev *led_cdev, ++ enum led_brightness value) ++{ ++ struct tm16xx_led *led = container_of(led_cdev, struct tm16xx_led, cdev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ if (value) ++ display->display_data[led->grid] |= (1 << led->segment); ++ else ++ display->display_data[led->grid] &= ~(1 << led->segment); ++ ++ schedule_work(&display->flush_display); ++} ++ ++/** ++ * tm16xx_display_value_show - Show current display value ++ * @dev: Pointer to device structure ++ * @attr: Pointer to device attribute structure ++ * @buf: Buffer to write the display value ++ * ++ * Return: Number of bytes written to buffer ++ */ ++static ssize_t tm16xx_display_value_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i; ++ ++ for (i = 0; i < display->num_digits && i < PAGE_SIZE - 1; i++) { ++ buf[i] = display->digits[i].value; ++ } ++ buf[i++] = '\n'; ++ ++ return i; ++} ++ ++/** ++ * tm16xx_display_value_store - Store new display value ++ * @dev: Pointer to device structure ++ * @attr: Pointer to device attribute structure ++ * @buf: Buffer containing the new display value ++ * @count: Number of bytes in buffer ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static ssize_t tm16xx_display_value_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ struct tm16xx_digit *digit; ++ int i; ++ u8 data; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ digit = &display->digits[i]; ++ ++ if (i < count && buf[i] != '\n') { ++ digit->value = buf[i]; ++ data = tm16xx_ascii_to_segments(display, digit->value); ++ } else { ++ digit->value = 0; ++ data = 0; ++ } ++ ++ display->display_data[digit->grid] = data; ++ } ++ ++ schedule_work(&display->flush_display); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_num_digits_show - Show the number of digits in the display ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the number of digits in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_num_digits_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ return sprintf(buf, "%d\n", display->num_digits); ++} ++ ++/** ++ * tm16xx_num_segments_show - Show the number of segments per digit ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the number of segments per digit in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_num_segments_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ return sprintf(buf, "%d\n", display->num_segments); ++} ++ ++/** ++ * tm16xx_segment_mapping_show - Show the current segment mapping ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current segment mapping of the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_segment_mapping_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i, count = 0; ++ ++ for (i = 0; i < display->num_segments; i++) { ++ count += sprintf(buf + count, "%d ", display->segment_mapping[i]); ++ } ++ count += sprintf(buf + count, "\n"); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_segment_mapping_store - Set a new segment mapping ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @count: Number of bytes in buf ++ * ++ * This function sets a new segment mapping for the display. ++ * It validates that each value is within the valid range, and that ++ * the number of received values matches the number of segments. ++ * ++ * Return: count on success, negative errno on failure ++ */ ++static ssize_t tm16xx_segment_mapping_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int *array, ret, i; ++ ++ ret = parse_int_array(buf, &array); ++ if (ret < 0) ++ return ret; ++ ++ if (ret != display->num_segments) { ++ kfree(array); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < display->num_segments; i++) { ++ if (array[i] < 0 || array[i] > 7) { ++ kfree(array); ++ return -EINVAL; ++ } ++ } ++ ++ for (i = 0; i < display->num_segments; i++) { ++ display->segment_mapping[i] = (u8) array[i]; ++ } ++ ++ kfree(array); ++ return count; ++} ++ ++/** ++ * tm16xx_digits_ordering_show - Show the current digit ordering ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current ordering of digits in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_digits_ordering_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i, count = 0; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ count += sprintf(buf + count, "%d ", display->digits[i].grid); ++ } ++ count += sprintf(buf + count, "\n"); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_digits_ordering_store - Set a new digit ordering ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @count: Number of bytes in buf ++ * ++ * This function sets a new ordering of digits for the display. ++ * It validates that all values match the original digit grid indexes, ++ * and that the number of received values matches the number of digits. ++ * ++ * Return: count on success, negative errno on failure ++ */ ++static ssize_t tm16xx_digits_ordering_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int *array, ret, i, j; ++ bool found; ++ ++ ret = parse_int_array(buf, &array); ++ if (ret < 0) ++ return ret; ++ ++ if (ret != display->num_digits) { ++ kfree(array); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < display->num_digits; i++) { ++ found = false; ++ ++ for (j = 0; j < display->num_digits; j++) { ++ if (display->digits[i].grid == array[j]) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ kfree(array); ++ return -EINVAL; ++ } ++ } ++ ++ for (i = 0; i < display->num_digits; i++) { ++ display->digits[i].grid = (u8) array[i]; ++ } ++ ++ kfree(array); ++ return count; ++} ++ ++/** ++ * tm16xx_map_seg7_show - Show the current 7-segment character map ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current 7-segment character map. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_map_seg7_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ memcpy(buf, &map_seg7, sizeof(map_seg7)); ++ return sizeof(map_seg7); ++} ++ ++/** ++ * tm16xx_map_seg7_store - Set a new 7-segment character map ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @cnt: Number of bytes in buf ++ * ++ * This function sets a new 7-segment character map. ++ * ++ * Return: cnt on success, negative errno on failure ++ */ ++static ssize_t tm16xx_map_seg7_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) ++{ ++ if(cnt != sizeof(map_seg7)) ++ return -EINVAL; ++ memcpy(&map_seg7, buf, cnt); ++ return cnt; ++} ++ ++static DEVICE_ATTR(value, 0644, tm16xx_display_value_show, tm16xx_display_value_store); ++static DEVICE_ATTR(num_digits, 0444, tm16xx_num_digits_show, NULL); ++static DEVICE_ATTR(num_segments, 0444, tm16xx_num_segments_show, NULL); ++static DEVICE_ATTR(segments, 0644, tm16xx_segment_mapping_show, tm16xx_segment_mapping_store); ++static DEVICE_ATTR(digits, 0644, tm16xx_digits_ordering_show, tm16xx_digits_ordering_store); ++static DEVICE_ATTR(map_seg7, 0644, tm16xx_map_seg7_show, tm16xx_map_seg7_store); ++ ++static struct attribute *tm16xx_main_led_attrs[] = { ++ &dev_attr_value.attr, ++ &dev_attr_num_digits.attr, ++ &dev_attr_num_segments.attr, ++ &dev_attr_segments.attr, ++ &dev_attr_digits.attr, ++ &dev_attr_map_seg7.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(tm16xx_main_led); ++ ++/** ++ * tm16xx_parse_dt - Parse device tree data ++ * @dev: Pointer to device structure ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_parse_dt(struct device *dev, struct tm16xx_display *display) ++{ ++ struct fwnode_handle *child; ++ int ret, i, max_grid = 0; ++ u8 *digits; ++ ++ ret = device_property_count_u8(dev, "tm16xx,digits"); ++ if (ret < 0) { ++ dev_err(dev, "Failed to count 'tm16xx,digits' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->num_digits = ret; ++ dev_dbg(dev, "Number of digits: %d\n", display->num_digits); ++ ++ digits = devm_kcalloc(dev, display->num_digits, sizeof(*digits), GFP_KERNEL); ++ if (!digits) ++ return -ENOMEM; ++ ++ ret = device_property_read_u8_array(dev, "tm16xx,digits", digits, display->num_digits); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'tm16xx,digits' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->digits = devm_kcalloc(dev, display->num_digits, sizeof(*display->digits), GFP_KERNEL); ++ if (!display->digits) ++ return -ENOMEM; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ display->digits[i].grid = digits[i]; ++ max_grid = umax(max_grid, digits[i]); ++ } ++ ++ devm_kfree(dev, digits); ++ ++ display->num_segments = device_property_count_u8(dev, "tm16xx,segment-mapping"); ++ if (display->num_segments < 0) { ++ dev_err(dev, "Failed to count 'tm16xx,segment-mapping' property: %d\n", display->num_segments); ++ return display->num_segments; ++ } ++ ++ dev_dbg(dev, "Number of segments: %d\n", display->num_segments); ++ ++ display->segment_mapping = devm_kcalloc(dev, display->num_segments, sizeof(*display->segment_mapping), GFP_KERNEL); ++ if (!display->segment_mapping) ++ return -ENOMEM; ++ ++ ret = device_property_read_u8_array(dev, "tm16xx,segment-mapping", display->segment_mapping, display->num_segments); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'tm16xx,segment-mapping' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->num_leds = 0; ++ device_for_each_child_node(dev, child) { ++ u32 reg[2]; ++ ++ ret = fwnode_property_read_u32_array(child, "reg", reg, 2); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'reg' property of led node: %d\n", ret); ++ return ret; ++ } ++ ++ max_grid = umax(max_grid, reg[0]); ++ display->num_leds++; ++ } ++ ++ dev_dbg(dev, "Number of LEDs: %d\n", display->num_leds); ++ ++ display->display_data_len = max_grid + 1; ++ dev_dbg(dev, "Number of display grids: %zu\n", display->display_data_len); ++ ++ display->display_data = devm_kcalloc(dev, display->display_data_len, sizeof(*display->display_data), GFP_KERNEL); ++ if (!display->display_data) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++/** ++ * tm16xx_probe - Probe function for tm16xx devices ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_probe(struct tm16xx_display *display) ++{ ++ struct device *dev = display->dev; ++ struct fwnode_handle *child; ++ int ret, i; ++ ++ ret = tm16xx_parse_dt(dev, display); ++ if (ret < 0) { ++ dev_err(dev, "Failed to parse device tree: %d\n", ret); ++ return ret; ++ } ++ ++ display->main_led.name = TM16XX_DEVICE_NAME; ++ display->main_led.brightness = display->controller->max_brightness; ++ display->main_led.max_brightness = display->controller->max_brightness; ++ display->main_led.brightness_set = tm16xx_brightness_set; ++ display->main_led.groups = tm16xx_main_led_groups; ++ display->main_led.flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; ++ ++ ret = devm_led_classdev_register(dev, &display->main_led); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register main LED: %d\n", ret); ++ return ret; ++ } ++ ++ display->leds = devm_kcalloc(dev, display->num_leds, sizeof(*display->leds), GFP_KERNEL); ++ if (!display->leds) ++ return -ENOMEM; ++ ++ i = 0; ++ device_for_each_child_node(dev, child) { ++ struct tm16xx_led *led = &display->leds[i]; ++ struct led_init_data led_init = { ++ .fwnode = child, ++ .devicename = display->main_led.name, ++ .devname_mandatory = true, ++ }; ++ u32 reg[2]; ++ ++ ret = fwnode_property_read_u32_array(child, "reg", reg, 2); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read LED reg property: %d\n", ret); ++ return ret; ++ } ++ ++ led->grid = reg[0]; ++ led->segment = reg[1]; ++ ++ led->cdev.max_brightness = 1; ++ led->cdev.brightness_set = tm16xx_led_set; ++ led->cdev.flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; ++ ++ ret = devm_led_classdev_register_ext(dev, &led->cdev, &led_init); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register LED %s: %d\n", led->cdev.name, ret); ++ return ret; ++ } ++ ++ i++; ++ } ++ ++ mutex_init(&display->lock); ++ INIT_WORK(&display->flush_brightness, tm16xx_display_flush_brightness); ++ INIT_WORK(&display->flush_display, tm16xx_display_flush_data); ++ ++ ret = tm16xx_display_init(display); ++ if (ret < 0) { ++ dev_err(display->dev, "Failed to initialize display: %d\n", ret); ++ return ret; ++ } ++ ++ dev_info(display->dev, "Display initialized successfully\n"); ++ return 0; ++} ++ ++/* SPI specific code */ ++static int tm16xx_spi_probe(struct spi_device *spi) ++{ ++ const struct tm16xx_controller *controller; ++ struct tm16xx_display *display; ++ int ret; ++ ++ controller = of_device_get_match_data(&spi->dev); ++ if (!controller) ++ return -EINVAL; ++ ++ display = devm_kzalloc(&spi->dev, sizeof(*display), GFP_KERNEL); ++ if (!display) ++ return -ENOMEM; ++ ++ display->client.spi = spi; ++ display->dev = &spi->dev; ++ display->controller = controller; ++ display->client_write = tm16xx_spi_write; ++ ++ spi_set_drvdata(spi, display); ++ ++ ret = tm16xx_probe(display); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void tm16xx_spi_remove(struct spi_device *spi) ++{ ++ struct tm16xx_display *display = spi_get_drvdata(spi); ++ tm16xx_display_remove(display); ++} ++ ++static const struct of_device_id tm16xx_spi_of_match[] = { ++ { .compatible = "titanmec,tm1618", .data = &tm1618_controller }, ++ { .compatible = "titanmec,tm1620", .data = &tm1628_controller }, ++ { .compatible = "titanmec,tm1628", .data = &tm1628_controller }, ++ { .compatible = "fdhisi,fd620", .data = &tm1628_controller }, ++ { .compatible = "fdhisi,fd628", .data = &tm1628_controller }, ++ { .compatible = "princeton,pt6964", .data = &tm1628_controller }, ++ { .compatible = "hbs,hbs658", .data = &hbs658_controller }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, tm16xx_spi_of_match); ++ ++static const struct spi_device_id tm16xx_spi_id[] = { ++ { "tm1618", 0 }, ++ { "tm1620", 0 }, ++ { "tm1628", 0 }, ++ { "fd620", 0 }, ++ { "fd628", 0 }, ++ { "pt6964", 0 }, ++ { "hbs658", 0 }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(spi, tm16xx_spi_id); ++ ++static struct spi_driver tm16xx_spi_driver = { ++ .driver = { ++ .name = TM16XX_DRIVER_NAME, ++ .of_match_table = tm16xx_spi_of_match, ++ }, ++ .probe = tm16xx_spi_probe, ++ .remove = tm16xx_spi_remove, ++ .shutdown = tm16xx_spi_remove, ++ .id_table = tm16xx_spi_id, ++}; ++ ++/* I2C specific code */ ++static int tm16xx_i2c_probe(struct i2c_client *client) ++{ ++ const struct tm16xx_controller *controller; ++ struct tm16xx_display *display; ++ int ret; ++ ++ controller = of_device_get_match_data(&client->dev); ++ if (!controller) ++ return -EINVAL; ++ ++ display = devm_kzalloc(&client->dev, sizeof(*display), GFP_KERNEL); ++ if (!display) ++ return -ENOMEM; ++ ++ display->client.i2c = client; ++ display->dev = &client->dev; ++ display->controller = controller; ++ display->client_write = tm16xx_i2c_write; ++ ++ i2c_set_clientdata(client, display); ++ ++ ret = tm16xx_probe(display); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void tm16xx_i2c_remove(struct i2c_client *client) ++{ ++ struct tm16xx_display *display = i2c_get_clientdata(client); ++ tm16xx_display_remove(display); ++} ++ ++static const struct of_device_id tm16xx_i2c_of_match[] = { ++ { .compatible = "titanmec,tm1650", .data = &tm1650_controller }, ++ { .compatible = "fdhisi,fd650", .data = &tm1650_controller }, ++ { .compatible = "fdhisi,fd6551", .data = &fd6551_controller }, ++ { .compatible = "fdhisi,fd655", .data = &fd655_controller }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, tm16xx_i2c_of_match); ++ ++static const struct i2c_device_id tm16xx_i2c_id[] = { ++ { "tm1650", 0 }, ++ { "fd650", 0 }, ++ { "fd6551", 0 }, ++ { "fd655", 0 }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(i2c, tm16xx_i2c_id); ++ ++static struct i2c_driver tm16xx_i2c_driver = { ++ .driver = { ++ .name = TM16XX_DRIVER_NAME, ++ .of_match_table = tm16xx_i2c_of_match, ++ }, ++ .probe = tm16xx_i2c_probe, ++ .remove = tm16xx_i2c_remove, ++ .shutdown = tm16xx_i2c_remove, ++ .id_table = tm16xx_i2c_id, ++}; ++ ++static int __init tm16xx_init(void) ++{ ++ int ret; ++ ++ ret = spi_register_driver(&tm16xx_spi_driver); ++ if (ret) ++ return ret; ++ ++ ret = i2c_add_driver(&tm16xx_i2c_driver); ++ if (ret) { ++ spi_unregister_driver(&tm16xx_spi_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void __exit tm16xx_exit(void) ++{ ++ i2c_del_driver(&tm16xx_i2c_driver); ++ spi_unregister_driver(&tm16xx_spi_driver); ++} ++ ++module_init(tm16xx_init); ++module_exit(tm16xx_exit); ++ ++MODULE_AUTHOR("Jean-François Lessard"); ++MODULE_DESCRIPTION("Auxiliary Display Driver for TM16XX and compatible LED controllers"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("spi:tm16xx"); ++MODULE_ALIAS("i2c:tm16xx"); +-- +2.34.1 + + diff --git a/patch/kernel/archive/rockchip64-6.11/overlay/Makefile b/patch/kernel/archive/rockchip64-6.11/overlay/Makefile index be8b1f59e..f6138e1eb 100644 --- a/patch/kernel/archive/rockchip64-6.11/overlay/Makefile +++ b/patch/kernel/archive/rockchip64-6.11/overlay/Makefile @@ -14,6 +14,7 @@ dtbo-$(CONFIG_ARCH_ROCKCHIP) += \ rockchip-rk3318-box-led-conf2.dtbo \ rockchip-rk3318-box-led-conf3.dtbo \ rockchip-rk3318-box-led-conf4.dtbo \ + rockchip-rk3318-box-led-conf5.dtbo \ rockchip-rk3318-box-wlan-ap6330.dtbo \ rockchip-rk3318-box-wlan-ap6334.dtbo \ rockchip-rk3318-box-wlan-ext.dtbo \ diff --git a/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf1.dtso b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf1.dtso index 26bfd7c63..fd15c8bb6 100644 --- a/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf1.dtso +++ b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf1.dtso @@ -7,7 +7,7 @@ /** * Generic rk3318 board with base common configuration. - * Some boards with this configuration have signature: YX_RK3318 (circular board), RK3318_V1.x + * Some boards with this configuration have signature: RK3318_V1.x */ &gpio_led { @@ -17,19 +17,6 @@ linux,default-trigger = "mmc2"; }; - /* - * no auxiliary led on YX_RK3328 boards - * - auxiliary { - gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_HIGH>; - label = "auxiliary"; - linux,default-trigger = "mmc2"; - default-state = "off"; - pinctrl-names = "default"; - pinctrl-0 = <&gpio_led_aux>; - }; - */ - }; /* diff --git a/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf2.dtso b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf2.dtso index c5040e6a1..5b6890a30 100644 --- a/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf2.dtso +++ b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf2.dtso @@ -4,27 +4,75 @@ #include #include #include +#include &gpio_led { working { gpios = <&gpio2 RK_PA6 GPIO_ACTIVE_LOW>; - linux,default-trigger = "mmc2"; + linux,default-trigger = "timer"; }; - /* - * no auxiliary led on X88_PRO_B boards - * - auxiliary { - gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_HIGH>; - label = "auxiliary"; - linux,default-trigger = "mmc2"; - default-state = "off"; - pinctrl-names = "default"; - pinctrl-0 = <&gpio_led_aux>; +}; + +&{/} { + + i2c_aux_display: i2c-aux-display { + + #address-cells = <1>; + #size-cells = <0>; + compatible = "spi-gpio"; + sck-gpios = <&gpio2 RK_PC3 GPIO_ACTIVE_HIGH>; + mosi-gpios = <&gpio2 RK_PC6 GPIO_ACTIVE_HIGH>; + cs-gpios = <&gpio2 RK_PC2 GPIO_ACTIVE_HIGH>; + num-chipselects = <1>; + + aux-display-controller@24 { + + compatible = "fdhisi,fd628"; + + reg = <0x24>; + spi-3wire; + spi-lsb-first; + spi-rx-delay-us = <1>; + spi-max-frequency = <500000>; + + tm16xx,digits = [00 01 02 03]; + tm16xx,segment-mapping = [03 01 02 06 04 05 00]; + + #address-cells = <2>; + #size-cells = <0>; + + led@4,3 { + reg = <4 3>; + function = LED_FUNCTION_POWER; + }; + + led@4,2 { + reg = <4 2>; + function = LED_FUNCTION_LAN; + linux,default-trigger = "stmmac-0:00:link"; + }; + + led@4,4 { + reg = <4 4>; + function = "colon"; + }; + + led@4,5 { + reg = <4 5>; + function = "wlan-lo"; + }; + + led@4,6 { + reg = <4 6>; + function = "wlan-hi"; + linux,default-trigger = "mmc1"; + }; + + }; }; - */ }; diff --git a/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf5.dtso b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf5.dtso new file mode 100644 index 000000000..3c305fec6 --- /dev/null +++ b/patch/kernel/archive/rockchip64-6.11/overlay/rockchip-rk3318-box-led-conf5.dtso @@ -0,0 +1,104 @@ +/dts-v1/; +/plugin/; + +#include +#include +#include +#include + +/** + * YX_RK3318 (circular) board + */ + +&gpio_led { + + working { + gpios = <&gpio2 RK_PC7 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "timer"; + }; + +}; + +&{/} { + + i2c_aux_display: i2c-aux-display { + + compatible = "i2c-gpio"; + sda-gpios = <&gpio2 RK_PC5 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + scl-gpios = <&gpio2 RK_PC6 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + i2c-gpio,delay-us = <2>; + i2c-gpio,sda-output-only; + i2c-gpio,scl-output-only; + #address-cells = <1>; + #size-cells = <0>; + + aux-display-controller@24 { + compatible = "fdhisi,fd6551"; + reg = <0x24>; + + tm16xx,digits = [04 03 02 01]; + tm16xx,segment-mapping = [00 01 02 03 04 05 06]; + + #address-cells = <2>; + #size-cells = <0>; + + led@0,0 { + reg = <0 0>; + function = LED_FUNCTION_ALARM; + }; + + led@0,1 { + reg = <0 1>; + function = "usb"; + linux,default-trigger = "usb-host"; + }; + + led@0,2 { + reg = <0 2>; + function = "pause"; + linux,default-trigger = "mmc2"; + }; + + led@0,3 { + reg = <0 3>; + function = "play"; + linux,default-trigger = "mmc0"; + }; + + led@0,4 { + reg = <0 4>; + function = "colon"; + }; + + led@0,5 { + reg = <0 5>; + function = LED_FUNCTION_LAN; + linux,default-trigger = "stmmac-0:00:link"; + }; + + led@0,6 { + reg = <0 6>; + function = LED_FUNCTION_WLAN; + linux,default-trigger = "mmc1"; + }; + + }; + }; + +}; + +/* + * TODO: needs to find the GPIO for this + * +&gpio_keys { + + reset { + gpios = <&gpio3 RK_PD1 GPIO_ACTIVE_LOW>; + label = "reset"; + linux,code = ; + debounce-interval = <200>; + wakeup-source; + }; + +}; +*/ diff --git a/patch/kernel/archive/rockchip64-6.6/general-driver-tm16xx-led-driver.patch b/patch/kernel/archive/rockchip64-6.6/general-driver-tm16xx-led-driver.patch new file mode 100644 index 000000000..e009ad77c --- /dev/null +++ b/patch/kernel/archive/rockchip64-6.6/general-driver-tm16xx-led-driver.patch @@ -0,0 +1,1221 @@ +From b305f05351984645293a9b7b2047779f6d8fbdd2 Mon Sep 17 00:00:00 2001 +From: Paolo Sabatino +Date: Sat, 5 Oct 2024 16:07:14 +0200 +Subject: [PATCH] Add tm16xx led auxiliary display driver + +--- + drivers/auxdisplay/Kconfig | 10 + + drivers/auxdisplay/Makefile | 1 + + drivers/auxdisplay/tm16xx.c | 1167 +++++++++++++++++++++++++++++++++++ + 3 files changed, 1178 insertions(+) + create mode 100644 drivers/auxdisplay/tm16xx.c + +diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig +index 64012cda4d12..3fe0088b7f1f 100644 +--- a/drivers/auxdisplay/Kconfig ++++ b/drivers/auxdisplay/Kconfig +@@ -183,6 +183,16 @@ config HT16K33 + Say yes here to add support for Holtek HT16K33, RAM mapping 16*8 + LED controller driver with keyscan. + ++config TM16XX ++ tristate "TM16xx/FD6xx LED controller driver and compatibles" ++ depends on I2C ++ select NEW_LEDS ++ select LEDS_CLASS ++ help ++ Say yes here to add support for Titanmicro TM1628, TM1650, ++ FudaHisi FD628, FD650, FD655, FD6551, RAM mapping 4*8/5*8 ++ LED controller drivers and compatibles ++ + config LCD2S + tristate "lcd2s 20x4 character display over I2C console" + depends on I2C +diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile +index 6968ed4d3f0a..460c7bdda2ae 100644 +--- a/drivers/auxdisplay/Makefile ++++ b/drivers/auxdisplay/Makefile +@@ -11,6 +11,7 @@ obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o + obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o + obj-$(CONFIG_HD44780) += hd44780.o + obj-$(CONFIG_HT16K33) += ht16k33.o ++obj-$(CONFIG_TM16XX) += tm16xx.o + obj-$(CONFIG_PARPORT_PANEL) += panel.o + obj-$(CONFIG_LCD2S) += lcd2s.o + obj-$(CONFIG_LINEDISP) += line-display.o +diff --git a/drivers/auxdisplay/tm16xx.c b/drivers/auxdisplay/tm16xx.c +new file mode 100644 +index 000000000000..d938b0166e74 +--- /dev/null ++++ b/drivers/auxdisplay/tm16xx.c +@@ -0,0 +1,1167 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Auxiliary Display Driver for TM16XX and compatible LED controllers ++ * ++ * Copyright (C) 2024 Jean-François Lessard ++ * ++ * This driver supports various LED controller chips, including TM16XX family, ++ * FD6XX family, PT6964, and HBS658. It provides support for both I2C and SPI interfaces. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TM16XX_DRIVER_NAME "tm16xx" ++#define TM16XX_DEVICE_NAME "display" ++ ++/* Forward declarations */ ++struct tm16xx_display; ++ ++/** ++ * struct tm16xx_controller - Controller-specific operations ++ * @max_brightness: Maximum brightness level supported by the controller ++ * @init: Initialize the controller ++ * @brightness: Set brightness level ++ * @data: Write display data ++ * ++ * This structure holds function pointers for controller-specific operations. ++ */ ++struct tm16xx_controller { ++ u8 max_brightness; ++ int (*init)(struct tm16xx_display *display, u8 **cmd); ++ int (*brightness)(struct tm16xx_display *display, u8 **cmd); ++ int (*data)(struct tm16xx_display *display, u8 **cmd, int data_index); ++}; ++ ++/** ++ * struct tm16xx_led - LED information ++ * @cdev: LED class device ++ * @grid: Grid index of the LED ++ * @segment: Segment index of the LED ++ */ ++struct tm16xx_led { ++ struct led_classdev cdev; ++ u8 grid; ++ u8 segment; ++}; ++ ++/** ++ * struct tm16xx_digit - Digit information ++ * @grid: Grid index of the digit ++ * @value: Current value of the digit ++ */ ++struct tm16xx_digit { ++ u8 grid; ++ char value; ++}; ++ ++/** ++ * struct tm16xx_display - Main driver structure ++ * @dev: Pointer to device structure ++ * @controller: Pointer to controller-specific operations ++ * @client: Union of I2C and SPI client structures ++ * @main_led: LED class device for the entire display ++ * @leds: Array of individual LEDs ++ * @num_leds: Number of LEDs ++ * @digits: Array of digits ++ * @num_digits: Number of digits ++ * @segment_mapping: Segment mapping array ++ * @num_segments: Number of segments ++ * @display_data: Display data buffer ++ * @display_data_len: Length of display data buffer ++ * @lock: Mutex for concurrent access protection ++ * @flush_brightness: Work structure for brightness update ++ * @flush_display: Work structure for display update ++ * @client_write: Function pointer for client write operation ++ */ ++struct tm16xx_display { ++ struct device *dev; ++ const struct tm16xx_controller *controller; ++ union { ++ struct i2c_client *i2c; ++ struct spi_device *spi; ++ } client; ++ struct led_classdev main_led; ++ struct tm16xx_led *leds; ++ int num_leds; ++ struct tm16xx_digit *digits; ++ int num_digits; ++ u8 *segment_mapping; ++ int num_segments; ++ u8 *display_data; ++ size_t display_data_len; ++ struct mutex lock; ++ struct work_struct flush_brightness; ++ struct work_struct flush_display; ++ int (*client_write)(struct tm16xx_display *display, u8 *data, size_t len); ++}; ++ ++/* Controller-specific functions */ ++static int tm1628_cmd_init(struct tm16xx_display *display, u8 **cmd) ++{ ++ // 01b mode is 5 grids with minimum of 7 segments ++ // tm1618 : 5 digits, 7 segments ++ // tm1620 : 5 digits, 9 segments ++ // tm1628 : 5 digits, 12 segments ++ static const u8 MODE_CMD = 0 << 7 | 0 << 6, MODE = 0 << 1 | 1 << 0; ++ static const u8 DATA_CMD = 0 << 7 | 1 << 6, DATA_ADDR_MODE = 0 << 2, DATA_WRITE_MODE = 0 << 1 | 0 << 0; ++ ++ static u8 cmds[] = { ++ MODE_CMD | MODE, ++ DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE, ++ }; ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1628_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[1]; ++ static const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = CTRL_CMD | ((i && 1) * (((i-1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1618_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static const u8 BYTE1_MASK = 0x1F, BYTE1_RSHIFT = 0; ++ static const u8 BYTE2_MASK = ~BYTE1_MASK, BYTE2_RSHIFT = 5-3; ++ static u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = (display->display_data[i] & BYTE1_MASK) >> BYTE1_RSHIFT; ++ cmds[2] = (display->display_data[i] & BYTE2_MASK) >> BYTE2_RSHIFT; ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1628_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ cmds[2] = 0; // SEG 9 to 14 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1650_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 4, SEG7_MODE = 1 << 3; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * ((i & BR_MASK) << BR_SHIFT | SEG7_MODE | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int tm1650_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 BASE_ADDR = 0x68; ++ static u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd655_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 3, BR_SHIFT = 5; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * (((i%3) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd655_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 BASE_ADDR = 0x66; ++ static u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + i * 2; ++ cmds[1] = display->display_data[i]; // SEG 1 to 8 ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int fd6551_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 1; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = 0x48; ++ cmds[1] = (i && 1) * ((~(i-1) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++void hbs658_msb_to_lsb(u8 *array, size_t length) { ++ for (size_t i = 0; i < length; i++) { ++ array[i] = (array[i] << 4) | (array[i] >> 4); ++ } ++} ++ ++static int hbs658_cmd_init(struct tm16xx_display *display, u8 **cmd) { ++ static const u8 DATA_CMD = 0 << 7 | 1 << 6, DATA_ADDR_MODE = 0 << 2, DATA_WRITE_MODE = 0 << 1 | 0 << 0; ++ ++ static u8 cmds[] = { ++ DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE, ++ }; ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int hbs658_cmd_brightness(struct tm16xx_display *display, u8 **cmd) { ++ static u8 cmds[1]; ++ static const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ ++ int i = display->main_led.brightness; ++ cmds[0] = CTRL_CMD | ((i && 1) * (((i-1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static int hbs658_cmd_data(struct tm16xx_display *display, u8 **cmd, int i) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ static u8 cmds[2]; ++ ++ cmds[0] = ADDR_CMD + i * 2; ++ cmds[1] = display->display_data[i]; ++ ++ hbs658_msb_to_lsb(cmds, ARRAY_SIZE(cmds)); ++ *cmd = cmds; ++ return ARRAY_SIZE(cmds); ++} ++ ++static const struct tm16xx_controller tm1618_controller = { ++ .max_brightness = 8, ++ .init = tm1628_cmd_init, ++ .brightness = tm1628_cmd_brightness, ++ .data = tm1618_cmd_data, ++}; ++ ++static const struct tm16xx_controller tm1628_controller = { ++ .max_brightness = 8, ++ .init = tm1628_cmd_init, ++ .brightness = tm1628_cmd_brightness, ++ .data = tm1628_cmd_data, ++}; ++ ++static const struct tm16xx_controller tm1650_controller = { ++ .max_brightness = 8, ++ .init = NULL, ++ .brightness = tm1650_cmd_brightness, ++ .data = tm1650_cmd_data, ++}; ++ ++static const struct tm16xx_controller fd655_controller = { ++ .max_brightness = 3, ++ .init = NULL, ++ .brightness = fd655_cmd_brightness, ++ .data = fd655_cmd_data, ++}; ++ ++static const struct tm16xx_controller fd6551_controller = { ++ .max_brightness = 8, ++ .init = NULL, ++ .brightness = fd6551_cmd_brightness, ++ .data = fd655_cmd_data, ++}; ++ ++static const struct tm16xx_controller hbs658_controller = { ++ .max_brightness = 8, ++ .init = hbs658_cmd_init, ++ .brightness = hbs658_cmd_brightness, ++ .data = hbs658_cmd_data, ++}; ++ ++static int parse_int_array(const char *buf, int **array) ++{ ++ int *values, value, count = 0, len; ++ const char *ptr = buf; ++ ++ while (1 == sscanf(ptr, "%d %n", &value, &len)) { ++ count++; ++ ptr += len; ++ } ++ ++ if (count == 0) { ++ *array = NULL; ++ return 0; ++ } ++ ++ values = kmalloc(count * sizeof(*values), GFP_KERNEL); ++ if (!values) ++ return -ENOMEM; ++ ++ ptr = buf; ++ count = 0; ++ while (1 == sscanf(ptr, "%d %n", &value, &len)) { ++ values[count++] = value; ++ ptr += len; ++ } ++ ++ *array = values; ++ return count; ++} ++ ++static SEG7_CONVERSION_MAP(map_seg7, MAP_ASCII7SEG_ALPHANUM); ++ ++/** ++ * tm16xx_ascii_to_segments - Convert ASCII character to segment pattern ++ * @display: Pointer to tm16xx_display structure ++ * @c: ASCII character to convert ++ * ++ * Return: Segment pattern for the given ASCII character ++ */ ++static u8 tm16xx_ascii_to_segments(struct tm16xx_display *display, char c) ++{ ++ u8 standard_segments, mapped_segments = 0; ++ int i; ++ ++ standard_segments = map_to_seg7(&map_seg7, c); ++ ++ for (i = 0; i < 7; i++) { ++ if (standard_segments & BIT(i)) ++ mapped_segments |= BIT(display->segment_mapping[i]); ++ } ++ ++ return mapped_segments; ++} ++ ++/** ++ * tm16xx_i2c_write - Write data to I2C client ++ * @display: Pointer to tm16xx_display structure ++ * @data: Data to write ++ * @len: Length of data ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static int tm16xx_i2c_write(struct tm16xx_display *display, u8 *data, size_t len) ++{ ++ dev_dbg(display->dev, "i2c_write %*ph", (char)len, data); ++ ++ struct i2c_msg msg = { ++ .addr = data[0] >> 1, ++ .flags = 0, ++ .len = len - 1, ++ .buf = &data[1], ++ }; ++ int ret; ++ ++ ret = i2c_transfer(display->client.i2c->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ ++ return (ret == 1) ? len : -EIO; ++} ++ ++/** ++ * tm16xx_spi_write - Write data to SPI client ++ * @display: Pointer to tm16xx_display structure ++ * @data: Data to write ++ * @len: Length of data ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static int tm16xx_spi_write(struct tm16xx_display *display, u8 *data, size_t len) ++{ ++ dev_dbg(display->dev, "spi_write %*ph", (char)len, data); ++ ++ struct spi_device *spi = display->client.spi; ++ ++ return spi_write(spi, data, len); ++} ++ ++/** ++ * tm16xx_display_flush_brightness - Work function to update brightness ++ * @work: Pointer to work_struct ++ */ ++static void tm16xx_display_flush_brightness(struct work_struct *work) ++{ ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_brightness); ++ u8 *cmd; ++ int len = -1, ret; ++ ++ if (display->controller->brightness) { ++ len = display->controller->brightness(display, &cmd); ++ } ++ ++ if (len > 0) { ++ mutex_lock(&display->lock); ++ ret = display->client_write(display, cmd, len); ++ mutex_unlock(&display->lock); ++ if (ret < 0) ++ dev_err(display->dev, "Failed to set brightness: %d\n", ret); ++ } ++} ++ ++/** ++ * tm16xx_display_flush_data - Work function to update display data ++ * @work: Pointer to work_struct ++ */ ++static void tm16xx_display_flush_data(struct work_struct *work) ++{ ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_display); ++ u8 *cmd; ++ int i, len = -1, ret; ++ ++ mutex_lock(&display->lock); ++ ++ for (i = 0; i < display->display_data_len; i++) { ++ if (display->controller->data) { ++ len = display->controller->data(display, &cmd, i); ++ } ++ ++ if (len > 0) { ++ ret = display->client_write(display, cmd, len); ++ if (ret < 0) { ++ dev_err(display->dev, "Failed to write display data: %d\n", ret); ++ break; ++ } ++ } ++ } ++ ++ mutex_unlock(&display->lock); ++} ++ ++/** ++ * tm16xx_display_init - Initialize the display ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_display_init(struct tm16xx_display *display) ++{ ++ u8 *cmd; ++ int len = -1, ret; ++ ++ if (display->controller->init) { ++ len = display->controller->init(display, &cmd); ++ } ++ ++ if (len > 0) { ++ mutex_lock(&display->lock); ++ ret = display->client_write(display, cmd, len); ++ mutex_unlock(&display->lock); ++ if (ret < 0) ++ return ret; ++ } ++ ++ schedule_work(&display->flush_brightness); ++ flush_work(&display->flush_brightness); ++ ++ memset(display->display_data, 0xFF, display->display_data_len); ++ schedule_work(&display->flush_display); ++ flush_work(&display->flush_display); ++ memset(display->display_data, 0x00, display->display_data_len); ++ ++ return 0; ++} ++ ++/** ++ * tm16xx_display_remove - Remove the display ++ * @display: Pointer to tm16xx_display structure ++ */ ++static void tm16xx_display_remove(struct tm16xx_display *display) ++{ ++ memset(display->display_data, 0x00, display->display_data_len); ++ schedule_work(&display->flush_display); ++ flush_work(&display->flush_display); ++ ++ display->main_led.brightness = LED_OFF; ++ schedule_work(&display->flush_brightness); ++ flush_work(&display->flush_brightness); ++ ++ dev_info(display->dev, "Display turned off\n"); ++} ++ ++/** ++ * tm16xx_brightness_set - Set brightness of the display ++ * @led_cdev: Pointer to led_classdev ++ * @brightness: Brightness value to set ++ */ ++static void tm16xx_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ led_cdev->brightness = brightness; ++ schedule_work(&display->flush_brightness); ++} ++ ++/** ++ * tm16xx_led_set - Set state of an individual LED ++ * @led_cdev: Pointer to led_classdev ++ * @value: Value to set (on/off) ++ */ ++static void tm16xx_led_set(struct led_classdev *led_cdev, ++ enum led_brightness value) ++{ ++ struct tm16xx_led *led = container_of(led_cdev, struct tm16xx_led, cdev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ if (value) ++ display->display_data[led->grid] |= (1 << led->segment); ++ else ++ display->display_data[led->grid] &= ~(1 << led->segment); ++ ++ schedule_work(&display->flush_display); ++} ++ ++/** ++ * tm16xx_display_value_show - Show current display value ++ * @dev: Pointer to device structure ++ * @attr: Pointer to device attribute structure ++ * @buf: Buffer to write the display value ++ * ++ * Return: Number of bytes written to buffer ++ */ ++static ssize_t tm16xx_display_value_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i; ++ ++ for (i = 0; i < display->num_digits && i < PAGE_SIZE - 1; i++) { ++ buf[i] = display->digits[i].value; ++ } ++ buf[i++] = '\n'; ++ ++ return i; ++} ++ ++/** ++ * tm16xx_display_value_store - Store new display value ++ * @dev: Pointer to device structure ++ * @attr: Pointer to device attribute structure ++ * @buf: Buffer containing the new display value ++ * @count: Number of bytes in buffer ++ * ++ * Return: Number of bytes written or negative error code ++ */ ++static ssize_t tm16xx_display_value_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ struct tm16xx_digit *digit; ++ int i; ++ u8 data; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ digit = &display->digits[i]; ++ ++ if (i < count && buf[i] != '\n') { ++ digit->value = buf[i]; ++ data = tm16xx_ascii_to_segments(display, digit->value); ++ } else { ++ digit->value = 0; ++ data = 0; ++ } ++ ++ display->display_data[digit->grid] = data; ++ } ++ ++ schedule_work(&display->flush_display); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_num_digits_show - Show the number of digits in the display ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the number of digits in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_num_digits_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ return sprintf(buf, "%d\n", display->num_digits); ++} ++ ++/** ++ * tm16xx_num_segments_show - Show the number of segments per digit ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the number of segments per digit in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_num_segments_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ ++ return sprintf(buf, "%d\n", display->num_segments); ++} ++ ++/** ++ * tm16xx_segment_mapping_show - Show the current segment mapping ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current segment mapping of the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_segment_mapping_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i, count = 0; ++ ++ for (i = 0; i < display->num_segments; i++) { ++ count += sprintf(buf + count, "%d ", display->segment_mapping[i]); ++ } ++ count += sprintf(buf + count, "\n"); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_segment_mapping_store - Set a new segment mapping ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @count: Number of bytes in buf ++ * ++ * This function sets a new segment mapping for the display. ++ * It validates that each value is within the valid range, and that ++ * the number of received values matches the number of segments. ++ * ++ * Return: count on success, negative errno on failure ++ */ ++static ssize_t tm16xx_segment_mapping_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int *array, ret, i; ++ ++ ret = parse_int_array(buf, &array); ++ if (ret < 0) ++ return ret; ++ ++ if (ret != display->num_segments) { ++ kfree(array); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < display->num_segments; i++) { ++ if (array[i] < 0 || array[i] > 7) { ++ kfree(array); ++ return -EINVAL; ++ } ++ } ++ ++ for (i = 0; i < display->num_segments; i++) { ++ display->segment_mapping[i] = (u8) array[i]; ++ } ++ ++ kfree(array); ++ return count; ++} ++ ++/** ++ * tm16xx_digits_ordering_show - Show the current digit ordering ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current ordering of digits in the display. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_digits_ordering_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int i, count = 0; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ count += sprintf(buf + count, "%d ", display->digits[i].grid); ++ } ++ count += sprintf(buf + count, "\n"); ++ ++ return count; ++} ++ ++/** ++ * tm16xx_digits_ordering_store - Set a new digit ordering ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @count: Number of bytes in buf ++ * ++ * This function sets a new ordering of digits for the display. ++ * It validates that all values match the original digit grid indexes, ++ * and that the number of received values matches the number of digits. ++ * ++ * Return: count on success, negative errno on failure ++ */ ++static ssize_t tm16xx_digits_ordering_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) ++{ ++ struct led_classdev *led_cdev = dev_get_drvdata(dev); ++ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); ++ int *array, ret, i, j; ++ bool found; ++ ++ ret = parse_int_array(buf, &array); ++ if (ret < 0) ++ return ret; ++ ++ if (ret != display->num_digits) { ++ kfree(array); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < display->num_digits; i++) { ++ found = false; ++ ++ for (j = 0; j < display->num_digits; j++) { ++ if (display->digits[i].grid == array[j]) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ kfree(array); ++ return -EINVAL; ++ } ++ } ++ ++ for (i = 0; i < display->num_digits; i++) { ++ display->digits[i].grid = (u8) array[i]; ++ } ++ ++ kfree(array); ++ return count; ++} ++ ++/** ++ * tm16xx_map_seg7_show - Show the current 7-segment character map ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The output buffer ++ * ++ * This function returns the current 7-segment character map. ++ * ++ * Return: Number of bytes written to buf ++ */ ++static ssize_t tm16xx_map_seg7_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ memcpy(buf, &map_seg7, sizeof(map_seg7)); ++ return sizeof(map_seg7); ++} ++ ++/** ++ * tm16xx_map_seg7_store - Set a new 7-segment character map ++ * @dev: The device struct ++ * @attr: The device_attribute struct ++ * @buf: The input buffer ++ * @cnt: Number of bytes in buf ++ * ++ * This function sets a new 7-segment character map. ++ * ++ * Return: cnt on success, negative errno on failure ++ */ ++static ssize_t tm16xx_map_seg7_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t cnt) ++{ ++ if(cnt != sizeof(map_seg7)) ++ return -EINVAL; ++ memcpy(&map_seg7, buf, cnt); ++ return cnt; ++} ++ ++static DEVICE_ATTR(value, 0644, tm16xx_display_value_show, tm16xx_display_value_store); ++static DEVICE_ATTR(num_digits, 0444, tm16xx_num_digits_show, NULL); ++static DEVICE_ATTR(num_segments, 0444, tm16xx_num_segments_show, NULL); ++static DEVICE_ATTR(segments, 0644, tm16xx_segment_mapping_show, tm16xx_segment_mapping_store); ++static DEVICE_ATTR(digits, 0644, tm16xx_digits_ordering_show, tm16xx_digits_ordering_store); ++static DEVICE_ATTR(map_seg7, 0644, tm16xx_map_seg7_show, tm16xx_map_seg7_store); ++ ++static struct attribute *tm16xx_main_led_attrs[] = { ++ &dev_attr_value.attr, ++ &dev_attr_num_digits.attr, ++ &dev_attr_num_segments.attr, ++ &dev_attr_segments.attr, ++ &dev_attr_digits.attr, ++ &dev_attr_map_seg7.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(tm16xx_main_led); ++ ++/** ++ * tm16xx_parse_dt - Parse device tree data ++ * @dev: Pointer to device structure ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_parse_dt(struct device *dev, struct tm16xx_display *display) ++{ ++ struct fwnode_handle *child; ++ int ret, i, max_grid = 0; ++ u8 *digits; ++ ++ ret = device_property_count_u8(dev, "tm16xx,digits"); ++ if (ret < 0) { ++ dev_err(dev, "Failed to count 'tm16xx,digits' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->num_digits = ret; ++ dev_dbg(dev, "Number of digits: %d\n", display->num_digits); ++ ++ digits = devm_kcalloc(dev, display->num_digits, sizeof(*digits), GFP_KERNEL); ++ if (!digits) ++ return -ENOMEM; ++ ++ ret = device_property_read_u8_array(dev, "tm16xx,digits", digits, display->num_digits); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'tm16xx,digits' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->digits = devm_kcalloc(dev, display->num_digits, sizeof(*display->digits), GFP_KERNEL); ++ if (!display->digits) ++ return -ENOMEM; ++ ++ for (i = 0; i < display->num_digits; i++) { ++ display->digits[i].grid = digits[i]; ++ max_grid = umax(max_grid, digits[i]); ++ } ++ ++ devm_kfree(dev, digits); ++ ++ display->num_segments = device_property_count_u8(dev, "tm16xx,segment-mapping"); ++ if (display->num_segments < 0) { ++ dev_err(dev, "Failed to count 'tm16xx,segment-mapping' property: %d\n", display->num_segments); ++ return display->num_segments; ++ } ++ ++ dev_dbg(dev, "Number of segments: %d\n", display->num_segments); ++ ++ display->segment_mapping = devm_kcalloc(dev, display->num_segments, sizeof(*display->segment_mapping), GFP_KERNEL); ++ if (!display->segment_mapping) ++ return -ENOMEM; ++ ++ ret = device_property_read_u8_array(dev, "tm16xx,segment-mapping", display->segment_mapping, display->num_segments); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'tm16xx,segment-mapping' property: %d\n", ret); ++ return ret; ++ } ++ ++ display->num_leds = 0; ++ device_for_each_child_node(dev, child) { ++ u32 reg[2]; ++ ++ ret = fwnode_property_read_u32_array(child, "reg", reg, 2); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read 'reg' property of led node: %d\n", ret); ++ return ret; ++ } ++ ++ max_grid = umax(max_grid, reg[0]); ++ display->num_leds++; ++ } ++ ++ dev_dbg(dev, "Number of LEDs: %d\n", display->num_leds); ++ ++ display->display_data_len = max_grid + 1; ++ dev_dbg(dev, "Number of display grids: %zu\n", display->display_data_len); ++ ++ display->display_data = devm_kcalloc(dev, display->display_data_len, sizeof(*display->display_data), GFP_KERNEL); ++ if (!display->display_data) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++/** ++ * tm16xx_probe - Probe function for tm16xx devices ++ * @display: Pointer to tm16xx_display structure ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int tm16xx_probe(struct tm16xx_display *display) ++{ ++ struct device *dev = display->dev; ++ struct fwnode_handle *child; ++ int ret, i; ++ ++ ret = tm16xx_parse_dt(dev, display); ++ if (ret < 0) { ++ dev_err(dev, "Failed to parse device tree: %d\n", ret); ++ return ret; ++ } ++ ++ display->main_led.name = TM16XX_DEVICE_NAME; ++ display->main_led.brightness = display->controller->max_brightness; ++ display->main_led.max_brightness = display->controller->max_brightness; ++ display->main_led.brightness_set = tm16xx_brightness_set; ++ display->main_led.groups = tm16xx_main_led_groups; ++ display->main_led.flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; ++ ++ ret = devm_led_classdev_register(dev, &display->main_led); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register main LED: %d\n", ret); ++ return ret; ++ } ++ ++ display->leds = devm_kcalloc(dev, display->num_leds, sizeof(*display->leds), GFP_KERNEL); ++ if (!display->leds) ++ return -ENOMEM; ++ ++ i = 0; ++ device_for_each_child_node(dev, child) { ++ struct tm16xx_led *led = &display->leds[i]; ++ struct led_init_data led_init = { ++ .fwnode = child, ++ .devicename = display->main_led.name, ++ .devname_mandatory = true, ++ }; ++ u32 reg[2]; ++ ++ ret = fwnode_property_read_u32_array(child, "reg", reg, 2); ++ if (ret < 0) { ++ dev_err(dev, "Failed to read LED reg property: %d\n", ret); ++ return ret; ++ } ++ ++ led->grid = reg[0]; ++ led->segment = reg[1]; ++ ++ led->cdev.max_brightness = 1; ++ led->cdev.brightness_set = tm16xx_led_set; ++ led->cdev.flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; ++ ++ ret = devm_led_classdev_register_ext(dev, &led->cdev, &led_init); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register LED %s: %d\n", led->cdev.name, ret); ++ return ret; ++ } ++ ++ i++; ++ } ++ ++ mutex_init(&display->lock); ++ INIT_WORK(&display->flush_brightness, tm16xx_display_flush_brightness); ++ INIT_WORK(&display->flush_display, tm16xx_display_flush_data); ++ ++ ret = tm16xx_display_init(display); ++ if (ret < 0) { ++ dev_err(display->dev, "Failed to initialize display: %d\n", ret); ++ return ret; ++ } ++ ++ dev_info(display->dev, "Display initialized successfully\n"); ++ return 0; ++} ++ ++/* SPI specific code */ ++static int tm16xx_spi_probe(struct spi_device *spi) ++{ ++ const struct tm16xx_controller *controller; ++ struct tm16xx_display *display; ++ int ret; ++ ++ controller = of_device_get_match_data(&spi->dev); ++ if (!controller) ++ return -EINVAL; ++ ++ display = devm_kzalloc(&spi->dev, sizeof(*display), GFP_KERNEL); ++ if (!display) ++ return -ENOMEM; ++ ++ display->client.spi = spi; ++ display->dev = &spi->dev; ++ display->controller = controller; ++ display->client_write = tm16xx_spi_write; ++ ++ spi_set_drvdata(spi, display); ++ ++ ret = tm16xx_probe(display); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void tm16xx_spi_remove(struct spi_device *spi) ++{ ++ struct tm16xx_display *display = spi_get_drvdata(spi); ++ tm16xx_display_remove(display); ++} ++ ++static const struct of_device_id tm16xx_spi_of_match[] = { ++ { .compatible = "titanmec,tm1618", .data = &tm1618_controller }, ++ { .compatible = "titanmec,tm1620", .data = &tm1628_controller }, ++ { .compatible = "titanmec,tm1628", .data = &tm1628_controller }, ++ { .compatible = "fdhisi,fd620", .data = &tm1628_controller }, ++ { .compatible = "fdhisi,fd628", .data = &tm1628_controller }, ++ { .compatible = "princeton,pt6964", .data = &tm1628_controller }, ++ { .compatible = "hbs,hbs658", .data = &hbs658_controller }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, tm16xx_spi_of_match); ++ ++static const struct spi_device_id tm16xx_spi_id[] = { ++ { "tm1618", 0 }, ++ { "tm1620", 0 }, ++ { "tm1628", 0 }, ++ { "fd620", 0 }, ++ { "fd628", 0 }, ++ { "pt6964", 0 }, ++ { "hbs658", 0 }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(spi, tm16xx_spi_id); ++ ++static struct spi_driver tm16xx_spi_driver = { ++ .driver = { ++ .name = TM16XX_DRIVER_NAME, ++ .of_match_table = tm16xx_spi_of_match, ++ }, ++ .probe = tm16xx_spi_probe, ++ .remove = tm16xx_spi_remove, ++ .shutdown = tm16xx_spi_remove, ++ .id_table = tm16xx_spi_id, ++}; ++ ++/* I2C specific code */ ++static int tm16xx_i2c_probe(struct i2c_client *client) ++{ ++ const struct tm16xx_controller *controller; ++ struct tm16xx_display *display; ++ int ret; ++ ++ controller = of_device_get_match_data(&client->dev); ++ if (!controller) ++ return -EINVAL; ++ ++ display = devm_kzalloc(&client->dev, sizeof(*display), GFP_KERNEL); ++ if (!display) ++ return -ENOMEM; ++ ++ display->client.i2c = client; ++ display->dev = &client->dev; ++ display->controller = controller; ++ display->client_write = tm16xx_i2c_write; ++ ++ i2c_set_clientdata(client, display); ++ ++ ret = tm16xx_probe(display); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void tm16xx_i2c_remove(struct i2c_client *client) ++{ ++ struct tm16xx_display *display = i2c_get_clientdata(client); ++ tm16xx_display_remove(display); ++} ++ ++static const struct of_device_id tm16xx_i2c_of_match[] = { ++ { .compatible = "titanmec,tm1650", .data = &tm1650_controller }, ++ { .compatible = "fdhisi,fd650", .data = &tm1650_controller }, ++ { .compatible = "fdhisi,fd6551", .data = &fd6551_controller }, ++ { .compatible = "fdhisi,fd655", .data = &fd655_controller }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, tm16xx_i2c_of_match); ++ ++static const struct i2c_device_id tm16xx_i2c_id[] = { ++ { "tm1650", 0 }, ++ { "fd650", 0 }, ++ { "fd6551", 0 }, ++ { "fd655", 0 }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(i2c, tm16xx_i2c_id); ++ ++static struct i2c_driver tm16xx_i2c_driver = { ++ .driver = { ++ .name = TM16XX_DRIVER_NAME, ++ .of_match_table = tm16xx_i2c_of_match, ++ }, ++ .probe = tm16xx_i2c_probe, ++ .remove = tm16xx_i2c_remove, ++ .shutdown = tm16xx_i2c_remove, ++ .id_table = tm16xx_i2c_id, ++}; ++ ++static int __init tm16xx_init(void) ++{ ++ int ret; ++ ++ ret = spi_register_driver(&tm16xx_spi_driver); ++ if (ret) ++ return ret; ++ ++ ret = i2c_add_driver(&tm16xx_i2c_driver); ++ if (ret) { ++ spi_unregister_driver(&tm16xx_spi_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void __exit tm16xx_exit(void) ++{ ++ i2c_del_driver(&tm16xx_i2c_driver); ++ spi_unregister_driver(&tm16xx_spi_driver); ++} ++ ++module_init(tm16xx_init); ++module_exit(tm16xx_exit); ++ ++MODULE_AUTHOR("Jean-François Lessard"); ++MODULE_DESCRIPTION("Auxiliary Display Driver for TM16XX and compatible LED controllers"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("spi:tm16xx"); ++MODULE_ALIAS("i2c:tm16xx"); +-- +2.34.1 + diff --git a/patch/kernel/archive/rockchip64-6.6/overlay/Makefile b/patch/kernel/archive/rockchip64-6.6/overlay/Makefile index 413cbef21..0504fb527 100644 --- a/patch/kernel/archive/rockchip64-6.6/overlay/Makefile +++ b/patch/kernel/archive/rockchip64-6.6/overlay/Makefile @@ -14,6 +14,7 @@ dtbo-$(CONFIG_ARCH_ROCKCHIP) += \ rockchip-rk3318-box-led-conf2.dtbo \ rockchip-rk3318-box-led-conf3.dtbo \ rockchip-rk3318-box-led-conf4.dtbo \ + rockchip-rk3318-box-led-conf5.dtbo \ rockchip-rk3318-box-wlan-ap6330.dtbo \ rockchip-rk3318-box-wlan-ap6334.dtbo \ rockchip-rk3318-box-wlan-ext.dtbo \ diff --git a/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf1.dts b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf1.dts index 26bfd7c63..fd15c8bb6 100644 --- a/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf1.dts +++ b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf1.dts @@ -7,7 +7,7 @@ /** * Generic rk3318 board with base common configuration. - * Some boards with this configuration have signature: YX_RK3318 (circular board), RK3318_V1.x + * Some boards with this configuration have signature: RK3318_V1.x */ &gpio_led { @@ -17,19 +17,6 @@ linux,default-trigger = "mmc2"; }; - /* - * no auxiliary led on YX_RK3328 boards - * - auxiliary { - gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_HIGH>; - label = "auxiliary"; - linux,default-trigger = "mmc2"; - default-state = "off"; - pinctrl-names = "default"; - pinctrl-0 = <&gpio_led_aux>; - }; - */ - }; /* diff --git a/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf2.dts b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf2.dts index c5040e6a1..97255dc02 100644 --- a/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf2.dts +++ b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf2.dts @@ -4,27 +4,75 @@ #include #include #include +#include &gpio_led { working { gpios = <&gpio2 RK_PA6 GPIO_ACTIVE_LOW>; - linux,default-trigger = "mmc2"; + linux,default-trigger = "timer"; }; - /* - * no auxiliary led on X88_PRO_B boards - * - auxiliary { - gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_HIGH>; - label = "auxiliary"; - linux,default-trigger = "mmc2"; - default-state = "off"; - pinctrl-names = "default"; - pinctrl-0 = <&gpio_led_aux>; +}; + +&{/} { + + i2c_aux_display: i2c-aux-display { + + #address-cells = <1>; + #size-cells = <0>; + compatible = "spi-gpio"; + sck-gpios = <&gpio2 RK_PC3 GPIO_ACTIVE_HIGH>; + mosi-gpios = <&gpio2 RK_PC6 GPIO_ACTIVE_HIGH>; + cs-gpios = <&gpio2 RK_PC2 GPIO_ACTIVE_HIGH>; + num-chipselects = <1>; + + aux-display-controller@24 { + + compatible = "fdhisi,fd628"; + + reg = <0x24>; + spi-3wire; + spi-lsb-first; + spi-rx-delay-us = <1>; + spi-max-frequency = <500000>; + + tm16xx,digits = [00 01 02 03]; + tm16xx,segment-mapping = [03 01 02 06 04 05 00]; + + #address-cells = <2>; + #size-cells = <0>; + + led@4,3 { + reg = <4 3>; + function = LED_FUNCTION_POWER; + }; + + led@4,2 { + reg = <4 2>; + function = LED_FUNCTION_LAN; + linux,default-trigger = "stmmac-1:00:link"; + }; + + led@4,4 { + reg = <4 4>; + function = "colon"; + }; + + led@4,5 { + reg = <4 5>; + function = "wlan-lo"; + }; + + led@4,6 { + reg = <4 6>; + function = "wlan-hi"; + linux,default-trigger = "mmc1"; + }; + + }; }; - */ }; diff --git a/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf5.dts b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf5.dts new file mode 100644 index 000000000..3f77b2920 --- /dev/null +++ b/patch/kernel/archive/rockchip64-6.6/overlay/rockchip-rk3318-box-led-conf5.dts @@ -0,0 +1,104 @@ +/dts-v1/; +/plugin/; + +#include +#include +#include +#include + +/** + * YX_RK3318 (circular) board + */ + +&gpio_led { + + working { + gpios = <&gpio2 RK_PC7 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "timer"; + }; + +}; + +&{/} { + + i2c_aux_display: i2c-aux-display { + + compatible = "i2c-gpio"; + sda-gpios = <&gpio2 RK_PC5 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + scl-gpios = <&gpio2 RK_PC6 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + i2c-gpio,delay-us = <2>; + i2c-gpio,sda-output-only; + i2c-gpio,scl-output-only; + #address-cells = <1>; + #size-cells = <0>; + + aux-display-controller@24 { + compatible = "fdhisi,fd6551"; + reg = <0x24>; + + tm16xx,digits = [04 03 02 01]; + tm16xx,segment-mapping = [00 01 02 03 04 05 06]; + + #address-cells = <2>; + #size-cells = <0>; + + led@0,0 { + reg = <0 0>; + function = LED_FUNCTION_ALARM; + }; + + led@0,1 { + reg = <0 1>; + function = "usb"; + linux,default-trigger = "usb-host"; + }; + + led@0,2 { + reg = <0 2>; + function = "pause"; + linux,default-trigger = "mmc2"; + }; + + led@0,3 { + reg = <0 3>; + function = "play"; + linux,default-trigger = "mmc0"; + }; + + led@0,4 { + reg = <0 4>; + function = "colon"; + }; + + led@0,5 { + reg = <0 5>; + function = LED_FUNCTION_LAN; + linux,default-trigger = "stmmac-1:00:link"; + }; + + led@0,6 { + reg = <0 6>; + function = LED_FUNCTION_WLAN; + linux,default-trigger = "mmc1"; + }; + + }; + }; + +}; + +/* + * TODO: needs to find the GPIO for this + * +&gpio_keys { + + reset { + gpios = <&gpio3 RK_PD1 GPIO_ACTIVE_LOW>; + label = "reset"; + linux,code = ; + debounce-interval = <200>; + wakeup-source; + }; + +}; +*/