diff --git a/patch/kernel/archive/rockchip64-6.15/general-driver-tm16xx-led-driver.patch b/patch/kernel/archive/rockchip64-6.15/general-driver-tm16xx-led-driver.patch index dc42841a45..5a4503d1b7 100644 --- a/patch/kernel/archive/rockchip64-6.15/general-driver-tm16xx-led-driver.patch +++ b/patch/kernel/archive/rockchip64-6.15/general-driver-tm16xx-led-driver.patch @@ -1,16 +1,175 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From 1f6c39c03f03b131f49d7d4631b1cda6c1f336e0 Mon Sep 17 00:00:00 2001 From: Paolo Sabatino -Date: Sat, 5 Oct 2024 16:07:14 +0200 -Subject: Add tm16xx led auxiliary display driver +Date: Sat, 21 Jun 2025 15:07:40 +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(+) + .../bindings/auxdisplay/tm16xx.yaml | 150 +++ + drivers/auxdisplay/Kconfig | 10 + + drivers/auxdisplay/Makefile | 1 + + drivers/auxdisplay/tm16xx.c | 1187 +++++++++++++++++ + 4 files changed, 1348 insertions(+) + create mode 100644 Documentation/devicetree/bindings/auxdisplay/tm16xx.yaml + create mode 100644 drivers/auxdisplay/tm16xx.c +diff --git a/Documentation/devicetree/bindings/auxdisplay/tm16xx.yaml b/Documentation/devicetree/bindings/auxdisplay/tm16xx.yaml +new file mode 100644 +index 000000000000..f3d411f4f4a0 +--- /dev/null ++++ b/Documentation/devicetree/bindings/auxdisplay/tm16xx.yaml +@@ -0,0 +1,150 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/auxdisplay/tm16xx.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Auxiliary displays based on TM16xx and compatible LED controllers ++ ++maintainers: ++ - Jean-François Lessard ++ ++description: | ++ TM16xx controllers manage a matrix of LEDs organized in grids (rows) and segments (columns). ++ Each grid or segment can be wired to drive either a digit or individual icons, depending on the ++ board design. ++ ++ Typical display example: ++ ++         ---    ---       ---    --- ++ WIFI  |   |  |   |  -  |   |  |   |  USB  PLAY ++       ---    ---       ---    --- ++ LAN   |   |  |   |  -  |   |  |   |  BT   PAUSE ++         ---    ---       ---    --- ++ ++ The controller itself is agnostic of the display layout. The specific arrangement ++ (which grids and segments drive which digits or icons) is determined by the board-level ++ wiring. Therefore, these bindings describe hardware configuration at the PCB level ++ to enable support of multiple display implementations using these LED controllers. ++ ++properties: ++ compatible: ++ enum: ++ - titanmec,tm1618 ++ - titanmec,tm1620 ++ - titanmec,tm1628 ++ - titanmec,tm1650 ++ - fdhisi,fd620 ++ - fdhisi,fd628 ++ - fdhisi,fd650 ++ - fdhisi,fd6551 ++ - fdhisi,fd655 ++ - princeton,pt6964 ++ - hbs,hbs658 ++ ++ reg: ++ maxItems: 1 ++ ++ tm16xx,digits: ++ description: | ++ Array of grid (row) indexes corresponding to specific wiring of digits in the display matrix. ++ Defines which grid lines are connected to digit elements. ++ $ref: /schemas/types.yaml#/definitions/uint8-array ++ items: ++ minimum: 0 ++ maximum: 7 ++ minItems: 1 ++ maxItems: 8 ++ ++ tm16xx,segment-mapping: ++ description: | ++ Array of segment (column) indexes specifying the hardware layout mapping used for digit display. ++ Each entry gives the segment index corresponding to a standard 7-segment element (a-g). ++ $ref: /schemas/types.yaml#/definitions/uint8-array ++ items: ++ minimum: 0 ++ maximum: 7 ++ minItems: 7 ++ maxItems: 7 ++ ++ tm16xx,transposed: ++ description: | ++ Optional flag indicating if grids and segments are swapped compared to standard matrix orientation. ++ This accommodates devices where segments are wired to rows and grids to columns. ++ $ref: /schemas/types.yaml#/definitions/flag ++ ++ "#address-cells": ++ const: 2 ++ ++ "#size-cells": ++ const: 0 ++ ++patternProperties: ++ "^led@[0-7],[0-7]$": ++ $ref: /schemas/leds/common.yaml# ++ properties: ++ reg: ++ description: Grid (row) and segment (column) index in the matrix of this individual LED icon ++ required: ++ - reg ++ ++required: ++ - compatible ++ - reg ++ - tm16xx,digits ++ - tm16xx,segment-mapping ++ ++additionalProperties: true ++ ++examples: ++ - | ++ display_client: i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ display@24 { ++ compatible = "titanmec,tm1650"; ++ reg = <0x24>; ++ tm16xx,digits = /bits/ 8 <0 1 2 3>; ++ tm16xx,segment-mapping = /bits/ 8 <0 1 2 3 4 5 6>; ++ ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ led@4,0 { ++ reg = <4 0>; ++ function = "lan"; ++ }; ++ ++ led@4,1 { ++ reg = <4 1>; ++ function = "wlan"; ++ }; ++ }; ++ }; ++ - | ++ display_client: spi { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ display@0 { ++ compatible = "titanmec,tm1628"; ++ reg = <0>; ++ tm16xx,transposed; ++ tm16xx,digits = /bits/ 8 <1 2 3 4>; ++ tm16xx,segment-mapping = /bits/ 8 <0 1 2 3 4 5 6>; ++ ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ led@0,2 { ++ reg = <0 2>; ++ function = "usb"; ++ }; ++ ++ led@0,3 { ++ reg = <0 3>; ++ function = "power"; ++ }; ++ }; ++ }; diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig -index 111111111111..222222222222 100644 +index bedc6133f970..4e4bb1a2b131 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -49,6 +49,16 @@ config HD44780 @@ -31,7 +190,7 @@ index 111111111111..222222222222 100644 tristate "lcd2s 20x4 character display over I2C console" depends on I2C diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile -index 111111111111..222222222222 100644 +index f5c13ed1cd4f..066f452b1e26 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_CHARLCD) += charlcd.o @@ -44,10 +203,10 @@ index 111111111111..222222222222 100644 obj-$(CONFIG_LCD2S) += lcd2s.o diff --git a/drivers/auxdisplay/tm16xx.c b/drivers/auxdisplay/tm16xx.c new file mode 100644 -index 000000000000..111111111111 +index 000000000000..72c4031f20d1 --- /dev/null +++ b/drivers/auxdisplay/tm16xx.c -@@ -0,0 +1,1167 @@ +@@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Auxiliary Display Driver for TM16XX and compatible LED controllers @@ -72,6 +231,13 @@ index 000000000000..111111111111 + +#define TM16XX_DRIVER_NAME "tm16xx" +#define TM16XX_DEVICE_NAME "display" ++#define DIGIT_SEGMENTS 7 ++#define MIN_SEGMENT 0 ++#define MAX_SEGMENT 7 /* data stored as 8 bits (u8) */ ++ ++static char *default_value = NULL; ++module_param(default_value, charp, 0644); ++MODULE_PARM_DESC(default_value, "Default display value to initialize"); + +/* Forward declarations */ +struct tm16xx_display; @@ -79,17 +245,15 @@ index 000000000000..111111111111 +/** + * 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 ++ * @init: Configures the controller mode and brightness ++ * @data: Writes display data to the controller + * + * 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); ++ const u8 max_brightness; ++ const int (*init)(struct tm16xx_display *display); ++ const int (*data)(struct tm16xx_display *display, u8 index, u8 data); +}; + +/** @@ -125,13 +289,14 @@ index 000000000000..111111111111 + * @digits: Array of digits + * @num_digits: Number of digits + * @segment_mapping: Segment mapping array -+ * @num_segments: Number of segments ++ * @digit_bitmask: Bitmask for setting digit values + * @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_init: Work structure for brightness update + * @flush_display: Work structure for display update -+ * @client_write: Function pointer for client write operation ++ * @flush_status: Result of the last flush work ++ * @transpose_display_data: Flag indicating if segments and grids should be transposed when writing data + */ +struct tm16xx_display { + struct device *dev; @@ -145,268 +310,17 @@ index 000000000000..111111111111 + int num_leds; + struct tm16xx_digit *digits; + int num_digits; -+ u8 *segment_mapping; -+ int num_segments; ++ u8 segment_mapping[DIGIT_SEGMENTS]; ++ u8 digit_bitmask; + u8 *display_data; + size_t display_data_len; + struct mutex lock; -+ struct work_struct flush_brightness; ++ struct work_struct flush_init; + struct work_struct flush_display; -+ int (*client_write)(struct tm16xx_display *display, u8 *data, size_t len); ++ int flush_status; ++ bool transpose_display_data; +}; + -+/* 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 @@ -451,23 +365,272 @@ index 000000000000..111111111111 + 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) ++/* Controller-specific functions */ ++static int tm1628_init(struct tm16xx_display *display) +{ -+ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_brightness); -+ u8 *cmd; -+ int len = -1, ret; ++ static const u8 MODE_CMD = 0 << 7 | 0 << 6; ++ static const u8 MODE_4GRIDS = 0 << 1 | 0 << 0; ++ static const u8 MODE_5GRIDS = 0 << 1 | 1 << 0; ++ static const u8 MODE_6GRIDS = 1 << 1 | 0 << 0; ++ static const u8 MODE_7GRIDS = 1 << 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 const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ const enum led_brightness brightness = display->main_led.brightness; ++ const u8 num_grids = display->transpose_display_data ? DIGIT_SEGMENTS : display->display_data_len; ++ u8 cmd; ++ int ret; + -+ if (display->controller->brightness) { -+ len = display->controller->brightness(display, &cmd); ++ cmd = MODE_CMD; ++ if (num_grids <= 4) { ++ cmd |= MODE_4GRIDS; ++ } else if (num_grids == 5) { ++ cmd |= MODE_5GRIDS; ++ } else if (num_grids == 6) { ++ cmd |= MODE_6GRIDS; ++ } else { ++ cmd |= MODE_7GRIDS; ++ } ++ ret = tm16xx_spi_write(display, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ cmd = DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE; ++ ret = tm16xx_spi_write(display, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ cmd = CTRL_CMD | ((brightness && 1) * (((brightness-1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ ret = tm16xx_spi_write(display, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int tm1618_data(struct tm16xx_display *display, u8 index, u8 data) { ++ 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; ++ u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + index * 2; ++ cmds[1] = (data & BYTE1_MASK) >> BYTE1_RSHIFT; ++ cmds[2] = (data & BYTE2_MASK) >> BYTE2_RSHIFT; ++ ++ return tm16xx_spi_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int tm1628_data(struct tm16xx_display *display, u8 index, u8 data) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ u8 cmds[3]; ++ ++ cmds[0] = ADDR_CMD + index * 2; ++ cmds[1] = data; // SEG 1 to 8 ++ cmds[2] = 0; // SEG 9 to 14 ++ ++ return tm16xx_spi_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int tm1650_init(struct tm16xx_display *display) { ++ u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 4, SEG8_MODE = 0 << 3; ++ /* SEG7_MODE = 1 << 3 ++ * tm1650 and fd650 have only 4 digits and they ++ * use an 8th segment for the time separator ++ * */ ++ const enum led_brightness brightness = display->main_led.brightness; ++ ++ cmds[0] = 0x48; ++ cmds[1] = (brightness && 1) * ((brightness & BR_MASK) << BR_SHIFT | SEG8_MODE | ON_FLAG); ++ ++ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int tm1650_data(struct tm16xx_display *display, u8 index, u8 data) { ++ static const u8 BASE_ADDR = 0x68; ++ u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + index * 2; ++ cmds[1] = data; // SEG 1 to 8 ++ ++ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int fd655_init(struct tm16xx_display *display) { ++ u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 3, BR_SHIFT = 5; ++ const enum led_brightness brightness = display->main_led.brightness; ++ ++ cmds[0] = 0x48; ++ cmds[1] = (brightness && 1) * (((brightness % 3) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int fd655_data(struct tm16xx_display *display, u8 index, u8 data) { ++ static const u8 BASE_ADDR = 0x66; ++ u8 cmds[2]; ++ ++ cmds[0] = BASE_ADDR + index * 2; ++ cmds[1] = data; // SEG 1 to 8 ++ ++ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static int fd6551_init(struct tm16xx_display *display) { ++ u8 cmds[2]; ++ static const u8 ON_FLAG = 1, BR_MASK = 7, BR_SHIFT = 1; ++ const enum led_brightness brightness = display->main_led.brightness; ++ ++ cmds[0] = 0x48; ++ cmds[1] = (brightness && 1) * ((~(brightness - 1) & BR_MASK) << BR_SHIFT | ON_FLAG); ++ ++ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static void hbs658_swap_nibbles(u8 *data, size_t len) ++{ ++ for (size_t i = 0; i < len; i++) { ++ data[i] = (data[i] << 4) | (data[i] >> 4); ++ } ++} ++ ++static int hbs658_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 const u8 CTRL_CMD = 1 << 7 | 0 << 6, ON_FLAG = 1 << 3, BR_MASK = 7, BR_SHIFT = 0; ++ const enum led_brightness brightness = display->main_led.brightness; ++ int ret; ++ ++ cmd = DATA_CMD | DATA_ADDR_MODE | DATA_WRITE_MODE; ++ hbs658_swap_nibbles(&cmd, 1); ++ ret = tm16xx_spi_write(display, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ cmd = CTRL_CMD | ((brightness && 1) * (((brightness - 1) & BR_MASK) << BR_SHIFT | ON_FLAG)); ++ hbs658_swap_nibbles(&cmd, 1); ++ ret = tm16xx_spi_write(display, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int hbs658_data(struct tm16xx_display *display, u8 index, u8 data) { ++ static const u8 ADDR_CMD = 1 << 7 | 1 << 6; ++ u8 cmds[2]; ++ ++ cmds[0] = ADDR_CMD + index * 2; ++ cmds[1] = data; ++ ++ hbs658_swap_nibbles(cmds, ARRAY_SIZE(cmds)); ++ return tm16xx_spi_write(display, cmds, ARRAY_SIZE(cmds)); ++} ++ ++static const struct tm16xx_controller tm1618_controller = { ++ .max_brightness = 8, ++ .init = tm1628_init, ++ .data = tm1618_data, ++}; ++ ++static const struct tm16xx_controller tm1628_controller = { ++ .max_brightness = 8, ++ .init = tm1628_init, ++ .data = tm1628_data, ++}; ++ ++static const struct tm16xx_controller tm1650_controller = { ++ .max_brightness = 8, ++ .init = tm1650_init, ++ .data = tm1650_data, ++}; ++ ++static const struct tm16xx_controller fd655_controller = { ++ .max_brightness = 3, ++ .init = fd655_init, ++ .data = fd655_data, ++}; ++ ++static const struct tm16xx_controller fd6551_controller = { ++ .max_brightness = 8, ++ .init = fd6551_init, ++ .data = fd655_data, ++}; ++ ++static const struct tm16xx_controller hbs658_controller = { ++ .max_brightness = 8, ++ .init = hbs658_init, ++ .data = hbs658_data, ++}; ++ ++static int tm16xx_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 (len > 0) { ++ 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 < DIGIT_SEGMENTS; i++) { ++ if (standard_segments & BIT(i)) ++ mapped_segments |= BIT(display->segment_mapping[i]); ++ } ++ ++ return mapped_segments; ++} ++ ++/** ++ * tm16xx_display_flush_init - Work function to update controller configuration (mode and brightness) ++ * @work: Pointer to work_struct ++ */ ++static void tm16xx_display_flush_init(struct work_struct *work) ++{ ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_init); ++ int ret; ++ ++ if (display->controller->init) { + mutex_lock(&display->lock); -+ ret = display->client_write(display, cmd, len); ++ ret = display->controller->init(display); ++ display->flush_status = ret; + mutex_unlock(&display->lock); + if (ret < 0) + dev_err(display->dev, "Failed to set brightness: %d\n", ret); @@ -481,18 +644,13 @@ index 000000000000..111111111111 +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; ++ int i, ret = 0; + + 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 (display->controller->data) { ++ for (i = 0; i < display->display_data_len; i++) { ++ ret = display->controller->data(display, i, display->display_data[i]); + if (ret < 0) { + dev_err(display->dev, "Failed to write display data: %d\n", ret); + break; @@ -500,41 +658,42 @@ index 000000000000..111111111111 + } + } + ++ display->flush_status = ret; + 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 ++ * tm16xx_display_flush_data_transposed - Transposed flush work function ++ * @work: Pointer to work_struct + */ -+static int tm16xx_display_init(struct tm16xx_display *display) ++static void tm16xx_display_flush_data_transposed(struct work_struct *work) +{ -+ u8 *cmd; -+ int len = -1, ret; ++ struct tm16xx_display *display = container_of(work, struct tm16xx_display, flush_display); ++ int i, j, ret = 0; ++ u8 transposed_data; + -+ if (display->controller->init) { -+ len = display->controller->init(display, &cmd); ++ mutex_lock(&display->lock); ++ ++ if (display->controller->data) { ++ /* Write operations based on number of segments */ ++ for (i = MIN_SEGMENT; i <= MAX_SEGMENT; i++) { ++ /* Gather bits from each grid for this segment */ ++ transposed_data = 0; ++ for (j = 0; j < display->display_data_len; j++) { ++ if (display->display_data[j] & BIT(i)) ++ transposed_data |= BIT(MAX_SEGMENT - j); ++ } ++ ++ ret = display->controller->data(display, i, transposed_data); ++ if (ret < 0) { ++ dev_err(display->dev, "Failed to write transposed data: %d\n", ret); ++ break; ++ } ++ } + } + -+ 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; ++ display->flush_status = ret; ++ mutex_unlock(&display->lock); +} + +/** @@ -548,8 +707,8 @@ index 000000000000..111111111111 + flush_work(&display->flush_display); + + display->main_led.brightness = LED_OFF; -+ schedule_work(&display->flush_brightness); -+ flush_work(&display->flush_brightness); ++ schedule_work(&display->flush_init); ++ flush_work(&display->flush_init); + + dev_info(display->dev, "Display turned off\n"); +} @@ -565,7 +724,7 @@ index 000000000000..111111111111 + struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); + + led_cdev->brightness = brightness; -+ schedule_work(&display->flush_brightness); ++ schedule_work(&display->flush_init); +} + +/** @@ -641,7 +800,9 @@ index 000000000000..111111111111 + data = 0; + } + -+ display->display_data[digit->grid] = data; ++ display->display_data[digit->grid] = ++ (display->display_data[digit->grid] & ~display->digit_bitmask) | ++ (data & display->digit_bitmask); + } + + schedule_work(&display->flush_display); @@ -668,24 +829,6 @@ index 000000000000..111111111111 +} + +/** -+ * 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 @@ -701,7 +844,7 @@ index 000000000000..111111111111 + struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); + int i, count = 0; + -+ for (i = 0; i < display->num_segments; i++) { ++ for (i = 0; i < DIGIT_SEGMENTS; i++) { + count += sprintf(buf + count, "%d ", display->segment_mapping[i]); + } + count += sprintf(buf + count, "\n"); @@ -728,24 +871,27 @@ index 000000000000..111111111111 + struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); + int *array, ret, i; + -+ ret = parse_int_array(buf, &array); ++ ret = tm16xx_parse_int_array(buf, &array); + if (ret < 0) + return ret; + -+ if (ret != display->num_segments) { ++ if (ret != DIGIT_SEGMENTS) { + kfree(array); + return -EINVAL; + } + -+ for (i = 0; i < display->num_segments; i++) { -+ if (array[i] < 0 || array[i] > 7) { ++ for (i = 0; i < DIGIT_SEGMENTS; i++) { ++ if (array[i] < MIN_SEGMENT || ++ array[i] > MAX_SEGMENT) { + kfree(array); + return -EINVAL; + } + } + -+ for (i = 0; i < display->num_segments; i++) { ++ display->digit_bitmask = 0; ++ for (i = 0; i < DIGIT_SEGMENTS; i++) { + display->segment_mapping[i] = (u8) array[i]; ++ display->digit_bitmask |= BIT(display->segment_mapping[i]); + } + + kfree(array); @@ -796,7 +942,7 @@ index 000000000000..111111111111 + int *array, ret, i, j; + bool found; + -+ ret = parse_int_array(buf, &array); ++ ret = tm16xx_parse_int_array(buf, &array); + if (ret < 0) + return ret; + @@ -866,7 +1012,6 @@ index 000000000000..111111111111 + +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); @@ -874,7 +1019,6 @@ index 000000000000..111111111111 +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, @@ -883,6 +1027,35 @@ index 000000000000..111111111111 +ATTRIBUTE_GROUPS(tm16xx_main_led); + +/** ++ * 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) ++{ ++ schedule_work(&display->flush_init); ++ flush_work(&display->flush_init); ++ if (display->flush_status < 0) ++ return display->flush_status; ++ ++ if (default_value && strlen(default_value) > 0) { ++ tm16xx_display_value_store( ++ display->main_led.dev, NULL, ++ default_value, strlen(default_value)); ++ } else { ++ 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); ++ if (display->flush_status < 0) ++ return display->flush_status; ++ } ++ ++ return 0; ++} ++ ++/** + * tm16xx_parse_dt - Parse device tree data + * @dev: Pointer to device structure + * @display: Pointer to tm16xx_display structure @@ -895,6 +1068,8 @@ index 000000000000..111111111111 + int ret, i, max_grid = 0; + u8 *digits; + ++ display->transpose_display_data = device_property_read_bool(dev, "tm16xx,transposed"); ++ + ret = device_property_count_u8(dev, "tm16xx,digits"); + if (ret < 0) { + dev_err(dev, "Failed to count 'tm16xx,digits' property: %d\n", ret); @@ -925,24 +1100,23 @@ index 000000000000..111111111111 + + 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); ++ ret = device_property_read_u8_array(dev, "tm16xx,segment-mapping", display->segment_mapping, DIGIT_SEGMENTS); + if (ret < 0) { + dev_err(dev, "Failed to read 'tm16xx,segment-mapping' property: %d\n", ret); + return ret; + } + ++ display->digit_bitmask = 0; ++ for (i = 0; i < DIGIT_SEGMENTS; i++) { ++ if (display->segment_mapping[i] < MIN_SEGMENT || ++ display->segment_mapping[i] > MAX_SEGMENT) { ++ dev_err(dev, "Invalid 'tm16xx,segment-mapping' value: %d (must be between %d and %d)\n", display->segment_mapping[i], MIN_SEGMENT, MAX_SEGMENT); ++ return -EINVAL; ++ } ++ ++ display->digit_bitmask |= BIT(display->segment_mapping[i]); ++ } ++ + display->num_leds = 0; + device_for_each_child_node(dev, child) { + u32 reg[2]; @@ -988,8 +1162,15 @@ index 000000000000..111111111111 + } + + mutex_init(&display->lock); -+ INIT_WORK(&display->flush_brightness, tm16xx_display_flush_brightness); -+ INIT_WORK(&display->flush_display, tm16xx_display_flush_data); ++ INIT_WORK(&display->flush_init, tm16xx_display_flush_init); ++ ++ /* Initialize work structure with appropriate flush function */ ++ if (display->transpose_display_data) { ++ INIT_WORK(&display->flush_display, tm16xx_display_flush_data_transposed); ++ dev_info(display->dev, "Operating in transposed mode\n"); ++ } else { ++ INIT_WORK(&display->flush_display, tm16xx_display_flush_data); ++ } + + display->main_led.name = TM16XX_DEVICE_NAME; + display->main_led.brightness = display->controller->max_brightness; @@ -1068,7 +1249,6 @@ index 000000000000..111111111111 + display->client.spi = spi; + display->dev = &spi->dev; + display->controller = controller; -+ display->client_write = tm16xx_spi_write; + + spi_set_drvdata(spi, display); + @@ -1138,7 +1318,6 @@ index 000000000000..111111111111 + display->client.i2c = client; + display->dev = &client->dev; + display->controller = controller; -+ display->client_write = tm16xx_i2c_write; + + i2c_set_clientdata(client, display); + @@ -1216,5 +1395,5 @@ index 000000000000..111111111111 +MODULE_ALIAS("spi:tm16xx"); +MODULE_ALIAS("i2c:tm16xx"); -- -Armbian +2.43.0