* add DFI driver to provide hardware-based memory controller load
* adapted DRAM Memory Controller driver from rk3328, add necessary headers
* provide device tree overlays to enable DDR3 clock scaling
* adapted rk322x-box to allow DDR speed bin selection to user
* add ipb900 board gpio configuration
* add rtl8723cs driver to linux-current kernel
* use ddrbin v1.10 set to 330 Mhz at boot
* provide overlays for ddr3 at 330, 528, 660 and 800 mhz
* split emmc overlays to enable options by user choice
* modified rk322x-config to support dmc, emmc overlays
* remove optee trust os in favor of rockchip proprietary blob
for dmc functionality
1306 lines
35 KiB
Plaintext
1306 lines
35 KiB
Plaintext
From dbda5943f5e06adc0ff828d028dbd666d9cd6063 Mon Sep 17 00:00:00 2001
|
|
From: Paolo Sabatino <paolo.sabatino@gmail.com>
|
|
Date: Sat, 15 May 2021 10:37:28 +0000
|
|
Subject: [PATCH] patching with tm1628 patch
|
|
|
|
---
|
|
.../bindings/leds/titanmec,tm1628.yaml | 134 ++++
|
|
.../devicetree/bindings/vendor-prefixes.yaml | 6 +
|
|
.../boot/dts/realtek/rtd1295-xnano-x5.dts | 78 ++
|
|
.../boot/dts/realtek/rtd1295-zidoo-x9s.dts | 36 +-
|
|
drivers/leds/Kconfig | 12 +
|
|
drivers/leds/Makefile | 1 +
|
|
drivers/leds/leds-tm1628.c | 727 ++++++++++++++++++
|
|
drivers/spi/spi-bitbang-txrx.h | 68 +-
|
|
drivers/spi/spi-gpio.c | 42 +-
|
|
9 files changed, 1092 insertions(+), 12 deletions(-)
|
|
create mode 100644 Documentation/devicetree/bindings/leds/titanmec,tm1628.yaml
|
|
create mode 100644 drivers/leds/leds-tm1628.c
|
|
|
|
diff --git a/Documentation/devicetree/bindings/leds/titanmec,tm1628.yaml b/Documentation/devicetree/bindings/leds/titanmec,tm1628.yaml
|
|
new file mode 100644
|
|
index 000000000..cf6c8d81e
|
|
--- /dev/null
|
|
+++ b/Documentation/devicetree/bindings/leds/titanmec,tm1628.yaml
|
|
@@ -0,0 +1,134 @@
|
|
+# SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
|
|
+%YAML 1.2
|
|
+---
|
|
+$id: http://devicetree.org/schemas/leds/titanmec,tm1628.yaml#
|
|
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
|
+
|
|
+title: Titan Micro Electronics TM1628 LED controller
|
|
+
|
|
+maintainers:
|
|
+ - Andreas Färber <afaerber@suse.de>
|
|
+
|
|
+properties:
|
|
+ compatible:
|
|
+ enum:
|
|
+ - fdhisi,fd628
|
|
+ - szfdwdz,aip1618
|
|
+ - titanmec,tm1628
|
|
+
|
|
+ reg:
|
|
+ maxItems: 1
|
|
+
|
|
+ "#grids":
|
|
+ description: |
|
|
+ Number of GRID output lines to use.
|
|
+ This limits the number of available SEG output lines.
|
|
+ minimum: 4
|
|
+ maximum: 7
|
|
+
|
|
+ "#address-cells":
|
|
+ const: 2
|
|
+
|
|
+ "#size-cells":
|
|
+ const: 0
|
|
+
|
|
+required:
|
|
+ - compatible
|
|
+ - reg
|
|
+
|
|
+patternProperties:
|
|
+ "^.*@[1-7],([1-9]|1[02-4])$":
|
|
+ type: object
|
|
+ description: |
|
|
+ Properties for a single LED.
|
|
+
|
|
+ properties:
|
|
+ reg:
|
|
+ description: |
|
|
+ 1-based grid number, followed by 1-based segment number.
|
|
+ maxItems: 1
|
|
+
|
|
+ linux,default-trigger: true
|
|
+
|
|
+ required:
|
|
+ - reg
|
|
+
|
|
+ "^display@([1-7],0|0,([1-9]|1[02-4]))$":
|
|
+ type: object
|
|
+ description: |
|
|
+ Properties for a sequence of 7-segment digits composed of multiple LEDs.
|
|
+
|
|
+ properties:
|
|
+ reg:
|
|
+ description: |
|
|
+ One or more tuples of grid number and segment number in visual order.
|
|
+ A segment of zero indicates that the corresponding grid output lines
|
|
+ represent the individual segments; a grid of zero indicates that the
|
|
+ corresponding segment output lines represent the individual segments.
|
|
+ minItems: 1
|
|
+ maxItems: 7
|
|
+
|
|
+ required:
|
|
+ - reg
|
|
+
|
|
+examples:
|
|
+ - |
|
|
+ #include <dt-bindings/leds/common.h>
|
|
+
|
|
+ spi {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ led-controller@0 {
|
|
+ compatible = "titanmec,tm1628";
|
|
+ reg = <0>;
|
|
+ spi-3-wire;
|
|
+ spi-lsb-first;
|
|
+ spi-max-frequency = <500000>;
|
|
+ #grids = <7>;
|
|
+ #address-cells = <2>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ display@0,8 {
|
|
+ reg = <0 8>, <0 7>, <0 6>, <0 5>;
|
|
+ };
|
|
+
|
|
+ colon@5,4 {
|
|
+ reg = <5 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ linux,default-trigger = "heartbeat";
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ - |
|
|
+ #include <dt-bindings/leds/common.h>
|
|
+
|
|
+ spi {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ led-controller@0 {
|
|
+ compatible = "titanmec,tm1628";
|
|
+ reg = <0>;
|
|
+ spi-3-wire;
|
|
+ spi-lsb-first;
|
|
+ spi-max-frequency = <500000>;
|
|
+ #grids = <6>;
|
|
+ #address-cells = <2>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ display@1,0 {
|
|
+ reg = <1 0>, <2 0>, <3 0>, <4 0>;
|
|
+ };
|
|
+
|
|
+ colon@5,4 {
|
|
+ reg = <5 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ linux,default-trigger = "heartbeat";
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+...
|
|
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
|
|
index 2735be1a8..078944371 100644
|
|
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
|
|
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
|
|
@@ -373,6 +373,8 @@ patternProperties:
|
|
description: Fastrax Oy
|
|
"^fcs,.*":
|
|
description: Fairchild Semiconductor
|
|
+ "^fdhisi,.*":
|
|
+ description: Fuzhou Fuda Hisi Microelectronics Co., Ltd.
|
|
"^feixin,.*":
|
|
description: Shenzhen Feixin Photoelectic Co., Ltd
|
|
"^feiyang,.*":
|
|
@@ -1045,6 +1047,8 @@ patternProperties:
|
|
description: Synaptics Inc.
|
|
"^synology,.*":
|
|
description: Synology, Inc.
|
|
+ "^szfdwdz,.*":
|
|
+ description: Shenzhen Fude Microelectronics Co., Ltd.
|
|
"^tbs,.*":
|
|
description: TBS Technologies
|
|
"^tbs-biometrics,.*":
|
|
@@ -1073,6 +1077,8 @@ patternProperties:
|
|
description: Texas Instruments
|
|
"^tianma,.*":
|
|
description: Tianma Micro-electronics Co., Ltd.
|
|
+ "^titanmec,.*":
|
|
+ description: Shenzhen Titan Micro Electronics Co., Ltd.
|
|
"^tlm,.*":
|
|
description: Trusted Logic Mobility
|
|
"^tmt,.*":
|
|
diff --git a/arch/arm64/boot/dts/realtek/rtd1295-xnano-x5.dts b/arch/arm64/boot/dts/realtek/rtd1295-xnano-x5.dts
|
|
index d7878ff94..07de95427 100644
|
|
--- a/arch/arm64/boot/dts/realtek/rtd1295-xnano-x5.dts
|
|
+++ b/arch/arm64/boot/dts/realtek/rtd1295-xnano-x5.dts
|
|
@@ -5,6 +5,9 @@
|
|
|
|
/dts-v1/;
|
|
|
|
+#include <dt-bindings/gpio/gpio.h>
|
|
+#include <dt-bindings/leds/common.h>
|
|
+
|
|
#include "rtd1295.dtsi"
|
|
|
|
/ {
|
|
@@ -23,6 +26,81 @@ aliases {
|
|
chosen {
|
|
stdout-path = "serial0:115200n8";
|
|
};
|
|
+
|
|
+ spi {
|
|
+ compatible = "spi-gpio";
|
|
+ sck-gpios = <&iso_gpio 4 GPIO_ACTIVE_HIGH>;
|
|
+ mosi-gpios = <&iso_gpio 3 GPIO_ACTIVE_HIGH>;
|
|
+ cs-gpios = <&iso_gpio 2 GPIO_ACTIVE_LOW>;
|
|
+ num-chipselects = <1>;
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ fd628: led-controller@0 {
|
|
+ compatible = "fdhisi,fd628";
|
|
+ reg = <0>;
|
|
+ spi-3wire;
|
|
+ spi-lsb-first;
|
|
+ spi-rx-delay-us = <1>;
|
|
+ spi-max-frequency = <500000>;
|
|
+ #grids = <7>;
|
|
+ #address-cells = <2>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ display@0,8 {
|
|
+ reg = <0 8>, <0 7>, <0 6>, <0 5>;
|
|
+ };
|
|
+
|
|
+ apps@1,4 {
|
|
+ reg = <1 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <1>;
|
|
+ };
|
|
+
|
|
+ setup@2,4 {
|
|
+ reg = <2 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <2>;
|
|
+ };
|
|
+
|
|
+ usb@3,4 {
|
|
+ reg = <3 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <3>;
|
|
+ };
|
|
+
|
|
+ card@4,4 {
|
|
+ reg = <4 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <4>;
|
|
+ };
|
|
+
|
|
+ colon@5,4 {
|
|
+ reg = <5 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_STATUS;
|
|
+ function-enumerator = <5>;
|
|
+ };
|
|
+
|
|
+ hdmi@6,4 {
|
|
+ reg = <6 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <6>;
|
|
+ };
|
|
+
|
|
+ cvbs@7,4 {
|
|
+ reg = <7 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_INDICATOR;
|
|
+ function-enumerator = <7>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
};
|
|
|
|
&uart0 {
|
|
diff --git a/arch/arm64/boot/dts/realtek/rtd1295-zidoo-x9s.dts b/arch/arm64/boot/dts/realtek/rtd1295-zidoo-x9s.dts
|
|
index 4beb37bb9..f1a082fc8 100644
|
|
--- a/arch/arm64/boot/dts/realtek/rtd1295-zidoo-x9s.dts
|
|
+++ b/arch/arm64/boot/dts/realtek/rtd1295-zidoo-x9s.dts
|
|
@@ -1,11 +1,12 @@
|
|
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
|
|
/*
|
|
- * Copyright (c) 2016-2017 Andreas Färber
|
|
+ * Copyright (c) 2016-2019 Andreas Färber
|
|
*/
|
|
|
|
/dts-v1/;
|
|
|
|
#include "rtd1295.dtsi"
|
|
+#include <dt-bindings/leds/common.h>
|
|
|
|
/ {
|
|
compatible = "zidoo,x9s", "realtek,rtd1295";
|
|
@@ -24,6 +25,39 @@ aliases {
|
|
chosen {
|
|
stdout-path = "serial0:115200n8";
|
|
};
|
|
+
|
|
+ spi {
|
|
+ compatible = "spi-gpio";
|
|
+ sck-gpios = <&iso_gpio 4 GPIO_ACTIVE_HIGH>;
|
|
+ mosi-gpios = <&iso_gpio 3 GPIO_ACTIVE_HIGH>;
|
|
+ cs-gpios = <&iso_gpio 2 GPIO_ACTIVE_LOW>;
|
|
+ num-chipselects = <1>;
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ tm1628: led-controller@0 {
|
|
+ compatible = "titanmec,tm1628";
|
|
+ reg = <0>;
|
|
+ spi-3wire;
|
|
+ spi-lsb-first;
|
|
+ spi-rx-delay-us = <1>;
|
|
+ spi-max-frequency = <500000>;
|
|
+ #grids = <7>;
|
|
+ #address-cells = <2>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ display@0,10 {
|
|
+ reg = <0 10>, <0 9>, <0 8>, <0 7>;
|
|
+ };
|
|
+
|
|
+ colon@5,4 {
|
|
+ reg = <5 4>;
|
|
+ color = <LED_COLOR_ID_WHITE>;
|
|
+ function = LED_FUNCTION_STATUS;
|
|
+ function-enumerator = <5>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
};
|
|
|
|
&uart0 {
|
|
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
|
|
index 849d3c5f9..e374a9686 100644
|
|
--- a/drivers/leds/Kconfig
|
|
+++ b/drivers/leds/Kconfig
|
|
@@ -928,6 +928,18 @@ config LEDS_ACER_A500
|
|
This option enables support for the Power Button LED of
|
|
Acer Iconia Tab A500.
|
|
|
|
+config LEDS_TM1628
|
|
+ tristate "LED driver for TM1628"
|
|
+ depends on LEDS_CLASS
|
|
+ depends on SPI
|
|
+ depends on OF || COMPILE_TEST
|
|
+ help
|
|
+ Say Y to enable support for Titan Micro Electronics TM1628,
|
|
+ Fuda Hisi Microelectronics FD628 and Fude Microelectronics AiP1618
|
|
+ LED controllers.
|
|
+ They are 3-wire SPI devices controlling a two-dimensional grid of
|
|
+ LEDs. Dimming is applied to all outputs through an internal PWM.
|
|
+
|
|
comment "LED Triggers"
|
|
source "drivers/leds/trigger/Kconfig"
|
|
|
|
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
|
|
index 73e603e17..eb35f48f2 100644
|
|
--- a/drivers/leds/Makefile
|
|
+++ b/drivers/leds/Makefile
|
|
@@ -99,6 +99,7 @@ obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o
|
|
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
|
|
obj-$(CONFIG_LEDS_EL15203000) += leds-el15203000.o
|
|
obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o
|
|
+obj-$(CONFIG_LEDS_TM1628) += leds-tm1628.o
|
|
|
|
# LED Userspace Drivers
|
|
obj-$(CONFIG_LEDS_USER) += uleds.o
|
|
diff --git a/drivers/leds/leds-tm1628.c b/drivers/leds/leds-tm1628.c
|
|
new file mode 100644
|
|
index 000000000..e28809d9a
|
|
--- /dev/null
|
|
+++ b/drivers/leds/leds-tm1628.c
|
|
@@ -0,0 +1,727 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+/*
|
|
+ * Titan Micro Electronics TM1628 LED controller
|
|
+ * Also compatible:
|
|
+ * Fuda Hisi Microelectronics FD628
|
|
+ * Fude Microelectronics AiP1618
|
|
+ *
|
|
+ * Copyright (c) 2019 Andreas Färber
|
|
+ */
|
|
+
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/leds.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/property.h>
|
|
+#include <linux/pwm.h>
|
|
+#include <linux/spi/spi.h>
|
|
+
|
|
+#define TM1628_CMD_MASK GENMASK(7, 6)
|
|
+#define TM1628_CMD_DISPLAY_MODE (0x0 << 6)
|
|
+#define TM1628_CMD_DATA_SETTING (0x1 << 6)
|
|
+#define TM1628_CMD_DISPLAY_CTRL (0x2 << 6)
|
|
+#define TM1628_CMD_ADDRESS_SETTING (0x3 << 6)
|
|
+
|
|
+#define TM1628_DISPLAY_MODE_MODE_MASK GENMASK(1, 0)
|
|
+
|
|
+#define TM1628_DATA_SETTING_MODE_MASK GENMASK(1, 0)
|
|
+#define TM1628_DATA_SETTING_WRITE_DATA 0x0
|
|
+#define TM1628_DATA_SETTING_READ_DATA 0x2
|
|
+#define TM1628_DATA_SETTING_FIXED_ADDR BIT(2)
|
|
+#define TM1628_DATA_SETTING_TEST_MODE BIT(3)
|
|
+
|
|
+#define TM1628_DISPLAY_CTRL_PW_MASK GENMASK(2, 0)
|
|
+
|
|
+#define TM1628_DISPLAY_CTRL_DISPLAY_ON BIT(3)
|
|
+
|
|
+struct tm1628_mode {
|
|
+ u8 grid_mask;
|
|
+ u16 seg_mask;
|
|
+};
|
|
+
|
|
+struct tm1628_info {
|
|
+ u8 grid_mask;
|
|
+ u16 seg_mask;
|
|
+ const struct tm1628_mode *modes;
|
|
+ int default_mode;
|
|
+ u8 k_mask;
|
|
+ u16 ks_mask;
|
|
+ const struct pwm_capture *pwm_map;
|
|
+ int default_pwm;
|
|
+};
|
|
+
|
|
+struct tm1628_segment {
|
|
+ u32 grid;
|
|
+ u32 seg;
|
|
+};
|
|
+
|
|
+struct tm1628_display {
|
|
+ struct tm1628_segment segments[8];
|
|
+};
|
|
+
|
|
+struct tm1628_led {
|
|
+ struct led_classdev leddev;
|
|
+ struct tm1628 *ctrl;
|
|
+ u32 grid;
|
|
+ u32 seg;
|
|
+};
|
|
+
|
|
+struct tm1628 {
|
|
+ struct spi_device *spi;
|
|
+ const struct tm1628_info *info;
|
|
+ u32 grids;
|
|
+ unsigned int segments;
|
|
+ int mode_index;
|
|
+ int pwm_index;
|
|
+ u8 data[14];
|
|
+ unsigned int num_displays;
|
|
+ struct tm1628_display *displays;
|
|
+ unsigned int num_leds;
|
|
+ struct tm1628_led leds[];
|
|
+};
|
|
+
|
|
+/* Command 1: Display Mode Setting */
|
|
+static int tm1628_set_display_mode(struct spi_device *spi, u8 grid_mode)
|
|
+{
|
|
+ u8 cmd = TM1628_CMD_DISPLAY_MODE;
|
|
+
|
|
+ if (unlikely(grid_mode & ~TM1628_DISPLAY_MODE_MODE_MASK))
|
|
+ return -EINVAL;
|
|
+
|
|
+ cmd |= grid_mode;
|
|
+
|
|
+ return spi_write(spi, &cmd, 1);
|
|
+}
|
|
+
|
|
+/* Command 2: Data Setting */
|
|
+static int tm1628_write_data(struct spi_device *spi, const u8 *data, unsigned int len)
|
|
+{
|
|
+ u8 cmd = TM1628_CMD_DATA_SETTING | TM1628_DATA_SETTING_WRITE_DATA;
|
|
+ struct spi_transfer xfers[] = {
|
|
+ {
|
|
+ .tx_buf = &cmd,
|
|
+ .len = 1,
|
|
+ },
|
|
+ {
|
|
+ .tx_buf = data,
|
|
+ .len = len,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ if (len > 14)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
|
|
+}
|
|
+
|
|
+/* Command 3: Address Setting */
|
|
+static int tm1628_set_address(struct spi_device *spi, u8 addr)
|
|
+{
|
|
+ u8 cmd = TM1628_CMD_ADDRESS_SETTING;
|
|
+
|
|
+ cmd |= (addr & GENMASK(3, 0));
|
|
+
|
|
+ return spi_write(spi, &cmd, 1);
|
|
+}
|
|
+
|
|
+/* Command 4: Display Control */
|
|
+static int tm1628_set_display_ctrl(struct spi_device *spi, bool on, u8 pwm_index)
|
|
+{
|
|
+ u8 cmd = TM1628_CMD_DISPLAY_CTRL;
|
|
+
|
|
+ if (on)
|
|
+ cmd |= TM1628_DISPLAY_CTRL_DISPLAY_ON;
|
|
+
|
|
+ if (pwm_index & ~TM1628_DISPLAY_CTRL_PW_MASK)
|
|
+ return -EINVAL;
|
|
+
|
|
+ cmd |= pwm_index;
|
|
+
|
|
+ return spi_write(spi, &cmd, 1);
|
|
+}
|
|
+
|
|
+static inline bool tm1628_is_valid_grid(struct tm1628 *s, unsigned int grid)
|
|
+{
|
|
+ return s->info->modes[s->mode_index].grid_mask & BIT(grid);
|
|
+}
|
|
+
|
|
+static inline bool tm1628_is_valid_seg(struct tm1628 *s, unsigned int seg)
|
|
+{
|
|
+ return s->info->modes[s->mode_index].seg_mask & BIT(seg);
|
|
+}
|
|
+
|
|
+static int tm1628_get_led_offset(struct tm1628 *s,
|
|
+ unsigned int grid, unsigned int seg, int *poffset, int *pbit)
|
|
+{
|
|
+ int offset, bit;
|
|
+
|
|
+ if (grid == 0 || grid > 7 || seg == 0 || seg > 16)
|
|
+ return -EINVAL;
|
|
+
|
|
+ offset = (grid - 1) * 2;
|
|
+ bit = seg - 1;
|
|
+ if (bit >= 8) {
|
|
+ bit -= 8;
|
|
+ offset++;
|
|
+ }
|
|
+
|
|
+ *poffset = offset;
|
|
+ if (pbit)
|
|
+ *pbit = bit;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tm1628_get_led(struct tm1628 *s,
|
|
+ unsigned int grid, unsigned int seg, bool *on)
|
|
+{
|
|
+ int offset, bit;
|
|
+ int ret;
|
|
+
|
|
+ ret = tm1628_get_led_offset(s, grid, seg, &offset, &bit);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ *on = !!(s->data[offset] & BIT(bit));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tm1628_set_led(struct tm1628 *s,
|
|
+ unsigned int grid, unsigned int seg, bool on)
|
|
+{
|
|
+ int offset, bit;
|
|
+ int ret;
|
|
+
|
|
+ ret = tm1628_get_led_offset(s, grid, seg, &offset, &bit);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (on)
|
|
+ s->data[offset] |= BIT(bit);
|
|
+ else
|
|
+ s->data[offset] &= ~BIT(bit);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tm1628_led_set_brightness(struct led_classdev *led_cdev,
|
|
+ enum led_brightness brightness)
|
|
+{
|
|
+ struct tm1628_led *led = container_of(led_cdev, struct tm1628_led, leddev);
|
|
+ struct tm1628 *s = led->ctrl;
|
|
+ int ret, offset;
|
|
+
|
|
+ ret = tm1628_set_led(s, led->grid, led->seg, brightness != LED_OFF);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = tm1628_get_led_offset(s, led->grid, led->seg, &offset, NULL);
|
|
+ if (unlikely(ret))
|
|
+ return ret;
|
|
+
|
|
+ ret = tm1628_set_address(s->spi, offset);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return tm1628_write_data(s->spi, s->data + offset, 1);
|
|
+}
|
|
+
|
|
+static enum led_brightness tm1628_led_get_brightness(struct led_classdev *led_cdev)
|
|
+{
|
|
+ struct tm1628_led *led = container_of(led_cdev, struct tm1628_led, leddev);
|
|
+ struct tm1628 *s = led->ctrl;
|
|
+ bool on;
|
|
+ int ret;
|
|
+
|
|
+ ret = tm1628_get_led(s, led->grid, led->seg, &on);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return on ? LED_ON : LED_OFF;
|
|
+}
|
|
+
|
|
+static int tm1628_register_led(struct tm1628 *s,
|
|
+ struct fwnode_handle *node, u32 grid, u32 seg, struct tm1628_led *led)
|
|
+{
|
|
+ struct device *dev = &s->spi->dev;
|
|
+ struct led_init_data init_data = {0};
|
|
+
|
|
+ if (!tm1628_is_valid_grid(s, grid) || !tm1628_is_valid_seg(s, seg)) {
|
|
+ dev_warn(dev, "%s reg out of range\n", fwnode_get_name(node));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ led->ctrl = s;
|
|
+ led->grid = grid;
|
|
+ led->seg = seg;
|
|
+ led->leddev.max_brightness = LED_ON;
|
|
+ led->leddev.brightness_set_blocking = tm1628_led_set_brightness;
|
|
+ led->leddev.brightness_get = tm1628_led_get_brightness;
|
|
+
|
|
+ fwnode_property_read_string(node, "linux,default-trigger", &led->leddev.default_trigger);
|
|
+
|
|
+ init_data.fwnode = node;
|
|
+ init_data.devicename = "tm1628";
|
|
+
|
|
+ return devm_led_classdev_register_ext(dev, &led->leddev, &init_data);
|
|
+}
|
|
+
|
|
+#define SSD_TOP BIT(0)
|
|
+#define SSD_TOP_RIGHT BIT(1)
|
|
+#define SSD_BOTTOM_RIGHT BIT(2)
|
|
+#define SSD_BOTTOM BIT(3)
|
|
+#define SSD_BOTTOM_LEFT BIT(4)
|
|
+#define SSD_TOP_LEFT BIT(5)
|
|
+#define SSD_MIDDLE BIT(6)
|
|
+#define SSD_DOT BIT(7)
|
|
+
|
|
+struct tm1628_ssd_char {
|
|
+ char ch;
|
|
+ u8 segs;
|
|
+};
|
|
+
|
|
+static const struct tm1628_ssd_char tm1628_char_ssd_map[] = {
|
|
+ { '0', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { '1', SSD_TOP_RIGHT | SSD_BOTTOM_RIGHT },
|
|
+ { '2', SSD_TOP | SSD_TOP_RIGHT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { '3', SSD_TOP | SSD_TOP_RIGHT | SSD_MIDDLE | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { '4', SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE | SSD_BOTTOM_RIGHT },
|
|
+ { '5', SSD_TOP | SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { '6', SSD_TOP | SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { '7', SSD_TOP | SSD_TOP_RIGHT | SSD_BOTTOM_RIGHT },
|
|
+ { '8', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { '9', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'A', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT },
|
|
+ { 'B', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'C', SSD_TOP | SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { 'D', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'E', SSD_TOP | SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { 'F', SSD_TOP | SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_LEFT },
|
|
+ { 'G', SSD_TOP | SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { 'H', SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT },
|
|
+ { 'I', SSD_TOP_LEFT | SSD_BOTTOM_LEFT },
|
|
+ { 'J', SSD_TOP | SSD_TOP_RIGHT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'K', SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT },
|
|
+ { 'L', SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { 'O', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'P', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT },
|
|
+ { 'Q', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'R', SSD_TOP | SSD_TOP_LEFT | SSD_TOP_RIGHT | SSD_MIDDLE |
|
|
+ SSD_BOTTOM_LEFT | SSD_BOTTOM_RIGHT },
|
|
+ { 'S', SSD_TOP | SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_RIGHT | SSD_BOTTOM },
|
|
+ { 'T', SSD_TOP | SSD_TOP_LEFT | SSD_BOTTOM_LEFT },
|
|
+ { 'U', SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT | SSD_TOP_RIGHT },
|
|
+ { 'V', SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT | SSD_TOP_RIGHT },
|
|
+ { 'W', SSD_TOP_LEFT | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT | SSD_TOP_RIGHT },
|
|
+ { 'Z', SSD_TOP | SSD_TOP_RIGHT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { 'b', SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { 'l', SSD_TOP_LEFT | SSD_BOTTOM_LEFT },
|
|
+ { 'o', SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { 't', SSD_TOP_LEFT | SSD_MIDDLE | SSD_BOTTOM_LEFT | SSD_BOTTOM },
|
|
+ { 'u', SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { 'v', SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { 'w', SSD_BOTTOM_LEFT | SSD_BOTTOM | SSD_BOTTOM_RIGHT },
|
|
+ { '-', SSD_MIDDLE },
|
|
+ { '_', SSD_BOTTOM },
|
|
+ { '.', SSD_DOT },
|
|
+};
|
|
+
|
|
+static u8 tm1628_get_char_ssd_map(char ch)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(tm1628_char_ssd_map); i++) {
|
|
+ if (tm1628_char_ssd_map[i].ch == ch)
|
|
+ return tm1628_char_ssd_map[i].segs;
|
|
+ }
|
|
+
|
|
+ return 0x0;
|
|
+}
|
|
+
|
|
+struct tm1628_ssd_glyph {
|
|
+ char *str;
|
|
+ u8 segs;
|
|
+};
|
|
+
|
|
+static const struct tm1628_ssd_glyph tm1628_glyph_ssd_map[] = {
|
|
+ { "ll", SSD_TOP_LEFT | SSD_BOTTOM_LEFT |
|
|
+ SSD_TOP_RIGHT | SSD_BOTTOM_RIGHT },
|
|
+};
|
|
+
|
|
+static u8 tm1628_get_glyph_ssd_map(const char *str)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(tm1628_glyph_ssd_map); i++) {
|
|
+ if (!strncmp(tm1628_glyph_ssd_map[i].str, str, 2))
|
|
+ return tm1628_glyph_ssd_map[i].segs;
|
|
+ }
|
|
+
|
|
+ return 0x0;
|
|
+}
|
|
+
|
|
+static int tm1628_display_apply_map(struct tm1628 *s,
|
|
+ struct tm1628_display *display, u8 map)
|
|
+{
|
|
+ struct tm1628_segment *segment;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ segment = &display->segments[i];
|
|
+ tm1628_set_led(s, segment->grid, segment->seg, map & BIT(i));
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t text_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct tm1628 *s = dev_get_drvdata(dev);
|
|
+ size_t offset, len = count;
|
|
+ u8 map, glyph_map;
|
|
+ int i, ret;
|
|
+
|
|
+ if (len > 0 && buf[len - 1] == '\n')
|
|
+ len--;
|
|
+
|
|
+ for (i = 0, offset = 0; i < s->num_displays; i++) {
|
|
+ if (offset < len) {
|
|
+ map = tm1628_get_char_ssd_map(buf[offset]);
|
|
+ if (offset + 1 < len && len > s->num_displays) {
|
|
+ glyph_map = tm1628_get_glyph_ssd_map(buf + offset);
|
|
+ if (glyph_map) {
|
|
+ map = glyph_map;
|
|
+ offset++;
|
|
+ }
|
|
+ }
|
|
+ offset++;
|
|
+ } else
|
|
+ map = 0x0;
|
|
+
|
|
+ tm1628_display_apply_map(s, &s->displays[i], map);
|
|
+ }
|
|
+
|
|
+ ret = tm1628_set_address(s->spi, 0x0);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = tm1628_write_data(s->spi, s->data, 14);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static struct device_attribute tm1628_attr =
|
|
+ __ATTR_WO(text);
|
|
+
|
|
+static int tm1628_register_display(struct tm1628 *s,
|
|
+ struct fwnode_handle *node)
|
|
+{
|
|
+ struct device *dev = &s->spi->dev;
|
|
+ struct tm1628_display *display;
|
|
+ u32 *reg;
|
|
+ u32 grid, seg;
|
|
+ int i, j, ret, reg_count;
|
|
+
|
|
+ reg_count = fwnode_property_count_u32(node, "reg");
|
|
+ if (reg_count < 0)
|
|
+ return reg_count;
|
|
+
|
|
+ if (reg_count % 2) {
|
|
+ dev_warn(dev, "Ignoring extra cell in %s reg property\n",
|
|
+ fwnode_get_name(node));
|
|
+ reg_count--;
|
|
+ }
|
|
+
|
|
+ if (s->displays) {
|
|
+ dev_warn(dev, "Only one display supported\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ s->num_displays = reg_count >> 1;
|
|
+
|
|
+ reg = devm_kzalloc(dev, reg_count * sizeof(*reg), GFP_KERNEL);
|
|
+ if (!reg)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ret = fwnode_property_read_u32_array(node, "reg", reg, reg_count);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Reading %s reg property failed (%d)\n",
|
|
+ fwnode_get_name(node), ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ s->displays = devm_kzalloc(dev, s->num_displays * sizeof(*s->displays), GFP_KERNEL);
|
|
+ if (!s->displays)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < s->num_displays; i++) {
|
|
+ display = &s->displays[i];
|
|
+ grid = reg[i * 2];
|
|
+ seg = reg[i * 2 + 1];
|
|
+ if (grid == 0 && seg != 0) {
|
|
+ if (!tm1628_is_valid_seg(s, seg)) {
|
|
+ dev_warn(dev, "%s reg out of range\n", fwnode_get_name(node));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ grid = s->info->modes[s->mode_index].grid_mask;
|
|
+ for (j = 0; grid && j < 7; j++) {
|
|
+ display->segments[j].seg = seg;
|
|
+ display->segments[j].grid = __ffs(grid);
|
|
+ grid &= ~BIT(display->segments[j].grid);
|
|
+ }
|
|
+ } else if (grid != 0 && seg == 0) {
|
|
+ if (!tm1628_is_valid_grid(s, grid)) {
|
|
+ dev_warn(dev, "%s reg out of range\n", fwnode_get_name(node));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ seg = s->info->modes[s->mode_index].seg_mask;
|
|
+ for (j = 0; seg && j < 8; j++) {
|
|
+ display->segments[j].grid = grid;
|
|
+ display->segments[j].seg = __ffs(seg);
|
|
+ seg &= ~BIT(display->segments[j].seg);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ devm_kfree(dev, reg);
|
|
+
|
|
+ device_create_file(dev, &tm1628_attr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Work around __builtin_popcount() */
|
|
+static u32 tm1628_grid_popcount(u8 grid_mask)
|
|
+{
|
|
+ int i, n = 0;
|
|
+
|
|
+ while (grid_mask) {
|
|
+ i = __ffs(grid_mask);
|
|
+ grid_mask &= ~BIT(i);
|
|
+ n++;
|
|
+ }
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static int tm1628_spi_probe(struct spi_device *spi)
|
|
+{
|
|
+ struct tm1628 *s;
|
|
+ struct fwnode_handle *child;
|
|
+ u32 grids;
|
|
+ u32 reg[2];
|
|
+ size_t leds;
|
|
+ int ret, i;
|
|
+
|
|
+ leds = device_get_child_node_count(&spi->dev);
|
|
+
|
|
+ s = devm_kzalloc(&spi->dev, struct_size(s, leds, leds), GFP_KERNEL);
|
|
+ if (!s)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ s->spi = spi;
|
|
+
|
|
+ s->info = device_get_match_data(&spi->dev);
|
|
+ if (!s->info)
|
|
+ return -EINVAL;
|
|
+
|
|
+ s->pwm_index = s->info->default_pwm;
|
|
+
|
|
+ ret = tm1628_set_display_ctrl(spi, false, s->pwm_index);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Turning display off failed (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = device_property_read_u32(&spi->dev, "#grids", &grids);
|
|
+ if (ret && ret != -EINVAL) {
|
|
+ dev_err(&spi->dev, "Error reading #grids property (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ s->mode_index = -1;
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ if (tm1628_grid_popcount(s->info->modes[i].grid_mask) != grids)
|
|
+ continue;
|
|
+ s->mode_index = i;
|
|
+ break;
|
|
+ }
|
|
+ if (s->mode_index == -1) {
|
|
+ dev_err(&spi->dev, "#grids out of range (%u)\n", grids);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ spi_set_drvdata(spi, s);
|
|
+
|
|
+ device_for_each_child_node(&spi->dev, child) {
|
|
+ ret = fwnode_property_read_u32_array(child, "reg", reg, 2);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Reading %s reg property failed (%d)\n",
|
|
+ fwnode_get_name(child), ret);
|
|
+ fwnode_handle_put(child);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (reg[0] != 0 && reg[1] != 0 && fwnode_property_count_u32(child, "reg") == 2) {
|
|
+ ret = tm1628_register_led(s, child, reg[0], reg[1], &s->leds[i++]);
|
|
+ if (ret && ret != -EINVAL) {
|
|
+ dev_err(&spi->dev, "Failed to register LED %s (%d)\n",
|
|
+ fwnode_get_name(child), ret);
|
|
+ fwnode_handle_put(child);
|
|
+ return ret;
|
|
+ }
|
|
+ s->num_leds++;
|
|
+ } else {
|
|
+ ret = tm1628_register_display(s, child);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Failed to register display %s (%d)\n",
|
|
+ fwnode_get_name(child), ret);
|
|
+ fwnode_handle_put(child);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = tm1628_set_address(spi, 0x0);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Setting address failed (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = tm1628_write_data(spi, s->data, sizeof(s->data));
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Writing data failed (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = tm1628_set_display_mode(spi, s->mode_index);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Setting display mode failed (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = tm1628_set_display_ctrl(spi, true, s->pwm_index);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "Turning display on failed (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct pwm_capture tm1628_pwm_map[8] = {
|
|
+ { .duty_cycle = 1, .period = 16 },
|
|
+ { .duty_cycle = 2, .period = 16 },
|
|
+ { .duty_cycle = 4, .period = 16 },
|
|
+ { .duty_cycle = 10, .period = 16 },
|
|
+ { .duty_cycle = 11, .period = 16 },
|
|
+ { .duty_cycle = 12, .period = 16 },
|
|
+ { .duty_cycle = 13, .period = 16 },
|
|
+ { .duty_cycle = 14, .period = 16 },
|
|
+};
|
|
+
|
|
+static const struct tm1628_mode tm1628_modes[4] = {
|
|
+ {
|
|
+ .grid_mask = GENMASK(4, 1),
|
|
+ .seg_mask = GENMASK(14, 12) | GENMASK(10, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(5, 1),
|
|
+ .seg_mask = GENMASK(13, 12) | GENMASK(10, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(6, 1),
|
|
+ .seg_mask = BIT(12) | GENMASK(10, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(7, 1),
|
|
+ .seg_mask = GENMASK(10, 1),
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct tm1628_info tm1628_info = {
|
|
+ .grid_mask = GENMASK(7, 1),
|
|
+ .seg_mask = GENMASK(14, 12) | GENMASK(10, 1),
|
|
+ .modes = tm1628_modes,
|
|
+ .default_mode = 3,
|
|
+ .k_mask = GENMASK(2, 1),
|
|
+ .ks_mask = GENMASK(10, 1),
|
|
+ .pwm_map = tm1628_pwm_map,
|
|
+ .default_pwm = 0,
|
|
+};
|
|
+
|
|
+static const struct tm1628_info fd628_info = {
|
|
+ .grid_mask = GENMASK(7, 1),
|
|
+ .seg_mask = GENMASK(14, 12) | GENMASK(10, 1),
|
|
+ .modes = tm1628_modes,
|
|
+ .default_mode = 3,
|
|
+ .k_mask = GENMASK(2, 1),
|
|
+ .ks_mask = GENMASK(10, 1),
|
|
+ .pwm_map = tm1628_pwm_map,
|
|
+ .default_pwm = 0,
|
|
+};
|
|
+
|
|
+static const struct tm1628_mode aip1618_modes[4] = {
|
|
+ {
|
|
+ .grid_mask = GENMASK(4, 1),
|
|
+ .seg_mask = GENMASK(8, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(5, 1),
|
|
+ .seg_mask = GENMASK(7, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(6, 1),
|
|
+ .seg_mask = GENMASK(6, 1),
|
|
+ },
|
|
+ {
|
|
+ .grid_mask = GENMASK(7, 1),
|
|
+ .seg_mask = GENMASK(5, 1),
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct tm1628_info aip1618_info = {
|
|
+ .grid_mask = GENMASK(7, 1),
|
|
+ .seg_mask = GENMASK(14, 12) | GENMASK(5, 1),
|
|
+ .modes = aip1618_modes,
|
|
+ .default_mode = 3,
|
|
+ .k_mask = BIT(2),
|
|
+ .ks_mask = GENMASK(5, 1),
|
|
+ .pwm_map = tm1628_pwm_map,
|
|
+ .default_pwm = 0,
|
|
+};
|
|
+
|
|
+static const struct of_device_id tm1628_spi_of_matches[] = {
|
|
+ { .compatible = "titanmec,tm1628", .data = &tm1628_info },
|
|
+ { .compatible = "fdhisi,fd628", .data = &fd628_info },
|
|
+ { .compatible = "szfdwdz,aip1618", .data = &aip1618_info },
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, tm1628_spi_of_matches);
|
|
+
|
|
+static struct spi_driver tm1628_spi_driver = {
|
|
+ .probe = tm1628_spi_probe,
|
|
+ .driver = {
|
|
+ .name = "tm1628",
|
|
+ .of_match_table = tm1628_spi_of_matches,
|
|
+ },
|
|
+};
|
|
+module_spi_driver(tm1628_spi_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("TM1628 LED controller driver");
|
|
+MODULE_AUTHOR("Andreas Färber");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/spi/spi-bitbang-txrx.h b/drivers/spi/spi-bitbang-txrx.h
|
|
index ae61d72c7..999a89325 100644
|
|
--- a/drivers/spi/spi-bitbang-txrx.h
|
|
+++ b/drivers/spi/spi-bitbang-txrx.h
|
|
@@ -45,7 +45,7 @@
|
|
|
|
static inline u32
|
|
bitbang_txrx_be_cpha0(struct spi_device *spi,
|
|
- unsigned nsecs, unsigned cpol, unsigned flags,
|
|
+ unsigned int nsecs, unsigned int cpol, unsigned int flags,
|
|
u32 word, u8 bits)
|
|
{
|
|
/* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
|
|
@@ -77,7 +77,7 @@ bitbang_txrx_be_cpha0(struct spi_device *spi,
|
|
|
|
static inline u32
|
|
bitbang_txrx_be_cpha1(struct spi_device *spi,
|
|
- unsigned nsecs, unsigned cpol, unsigned flags,
|
|
+ unsigned int nsecs, unsigned int cpol, unsigned int flags,
|
|
u32 word, u8 bits)
|
|
{
|
|
/* if (cpol == 0) this is SPI_MODE_1; else this is SPI_MODE_3 */
|
|
@@ -106,3 +106,67 @@ bitbang_txrx_be_cpha1(struct spi_device *spi,
|
|
}
|
|
return word;
|
|
}
|
|
+
|
|
+static inline u32
|
|
+bitbang_txrx_le_cpha0(struct spi_device *spi,
|
|
+ unsigned int nsecs, unsigned int cpol, unsigned int flags,
|
|
+ u32 word, u8 bits)
|
|
+{
|
|
+ /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
|
|
+
|
|
+ u32 oldbit = !(word & 1);
|
|
+ /* clock starts at inactive polarity */
|
|
+ for (; likely(bits); bits--) {
|
|
+
|
|
+ /* setup LSB (to slave) on trailing edge */
|
|
+ if ((flags & SPI_MASTER_NO_TX) == 0) {
|
|
+ if ((word & 1) != oldbit) {
|
|
+ setmosi(spi, word & 1);
|
|
+ oldbit = word & 1;
|
|
+ }
|
|
+ }
|
|
+ spidelay(nsecs); /* T(setup) */
|
|
+
|
|
+ setsck(spi, !cpol);
|
|
+ spidelay(nsecs);
|
|
+
|
|
+ /* sample LSB (from slave) on leading edge */
|
|
+ word >>= 1;
|
|
+ if ((flags & SPI_MASTER_NO_RX) == 0)
|
|
+ word |= getmiso(spi) << (bits - 1);
|
|
+ setsck(spi, cpol);
|
|
+ }
|
|
+ return word;
|
|
+}
|
|
+
|
|
+static inline u32
|
|
+bitbang_txrx_le_cpha1(struct spi_device *spi,
|
|
+ unsigned int nsecs, unsigned int cpol, unsigned int flags,
|
|
+ u32 word, u8 bits)
|
|
+{
|
|
+ /* if (cpol == 0) this is SPI_MODE_1; else this is SPI_MODE_3 */
|
|
+
|
|
+ u32 oldbit = !(word & 1);
|
|
+ /* clock starts at inactive polarity */
|
|
+ for (; likely(bits); bits--) {
|
|
+
|
|
+ /* setup LSB (to slave) on leading edge */
|
|
+ setsck(spi, !cpol);
|
|
+ if ((flags & SPI_MASTER_NO_TX) == 0) {
|
|
+ if ((word & 1) != oldbit) {
|
|
+ setmosi(spi, word & 1);
|
|
+ oldbit = word & 1;
|
|
+ }
|
|
+ }
|
|
+ spidelay(nsecs); /* T(setup) */
|
|
+
|
|
+ setsck(spi, cpol);
|
|
+ spidelay(nsecs);
|
|
+
|
|
+ /* sample LSB (from slave) on trailing edge */
|
|
+ word >>= 1;
|
|
+ if ((flags & SPI_MASTER_NO_RX) == 0)
|
|
+ word |= getmiso(spi) << (bits - 1);
|
|
+ }
|
|
+ return word;
|
|
+}
|
|
diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c
|
|
index 0584f4d2f..4b12c4964 100644
|
|
--- a/drivers/spi/spi-gpio.c
|
|
+++ b/drivers/spi/spi-gpio.c
|
|
@@ -135,25 +135,37 @@ static inline int getmiso(const struct spi_device *spi)
|
|
static u32 spi_gpio_txrx_word_mode0(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
- return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_txrx_word_mode1(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
- return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_txrx_word_mode2(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
- return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_txrx_word_mode3(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
- return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
}
|
|
|
|
/*
|
|
@@ -170,28 +182,40 @@ static u32 spi_gpio_spec_txrx_word_mode0(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
flags = spi->master->flags;
|
|
- return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha0(spi, nsecs, 0, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_spec_txrx_word_mode1(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
flags = spi->master->flags;
|
|
- return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha1(spi, nsecs, 0, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_spec_txrx_word_mode2(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
flags = spi->master->flags;
|
|
- return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha0(spi, nsecs, 1, flags, word, bits);
|
|
}
|
|
|
|
static u32 spi_gpio_spec_txrx_word_mode3(struct spi_device *spi,
|
|
unsigned nsecs, u32 word, u8 bits, unsigned flags)
|
|
{
|
|
flags = spi->master->flags;
|
|
- return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
+ if (unlikely(spi->mode & SPI_LSB_FIRST))
|
|
+ return bitbang_txrx_le_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
+ else
|
|
+ return bitbang_txrx_be_cpha1(spi, nsecs, 1, flags, word, bits);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
@@ -378,7 +402,7 @@ static int spi_gpio_probe(struct platform_device *pdev)
|
|
|
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
|
|
master->mode_bits = SPI_3WIRE | SPI_3WIRE_HIZ | SPI_CPHA | SPI_CPOL |
|
|
- SPI_CS_HIGH;
|
|
+ SPI_CS_HIGH | SPI_LSB_FIRST;
|
|
if (!spi_gpio->mosi) {
|
|
/* HW configuration without MOSI pin
|
|
*
|
|
--
|
|
2.25.1
|
|
|