rockchip64-6.19: auxdisplay: add a driver for Vinka VK2C21 Segment LCD Controller
- for the Mekotronics R58X Pro LCD - The Vinka VK2C21 (and pin-compatible VK2C21A/VK2C21B) is a memory-mapping segment LCD controller that communicates over I2C. It can drive up to 128 segments (e.g. 32 SEG × 4 COM in 1/3 bias mode). - Some SBCs wire this chip to GPIO pins without I2C pull-up resistors, making the standard i2c-gpio (open-drain) driver unusable. This driver therefore bit-bangs I2C in push-pull mode directly via GPIO, requiring no external pull-ups. - extra glyphs and fix for top-bar Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
This commit is contained in:
parent
692583a57c
commit
5ee03afdb5
@ -0,0 +1,651 @@
|
||||
From 8c7c8e1220f9ba4fecaba698bab176cd6ecca164 Mon Sep 17 00:00:00 2001
|
||||
From: Ricardo Pardini <ricardo@pardini.net>
|
||||
Date: Sun, 22 Feb 2026 21:48:32 -0300
|
||||
Subject: [PATCH] auxdisplay: add a driver for Vinka VK2C21 Segment LCD
|
||||
Controller
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The Vinka VK2C21 (and pin-compatible VK2C21A/VK2C21B) is a memory-mapping
|
||||
segment LCD controller that communicates over I2C. It can drive up to
|
||||
128 segments (e.g. 32 SEG × 4 COM in 1/3 bias mode).
|
||||
Some SBCs wire this chip to GPIO pins without I2C pull-up
|
||||
resistors, making the standard i2c-gpio (open-drain) driver unusable.
|
||||
This driver therefore bit-bangs I2C in push-pull mode directly via GPIO,
|
||||
requiring no external pull-ups.
|
||||
|
||||
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
|
||||
---
|
||||
.../bindings/auxdisplay/vinka,vk2c21.yaml | 59 ++
|
||||
drivers/auxdisplay/Kconfig | 13 +
|
||||
drivers/auxdisplay/Makefile | 1 +
|
||||
drivers/auxdisplay/lcd-vk2c21.c | 517 ++++++++++++++++++
|
||||
4 files changed, 590 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/auxdisplay/vinka,vk2c21.yaml
|
||||
create mode 100644 drivers/auxdisplay/lcd-vk2c21.c
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/auxdisplay/vinka,vk2c21.yaml b/Documentation/devicetree/bindings/auxdisplay/vinka,vk2c21.yaml
|
||||
new file mode 100644
|
||||
index 0000000000000..bdaeb1bd634d6
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/auxdisplay/vinka,vk2c21.yaml
|
||||
@@ -0,0 +1,59 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/auxdisplay/vinka,vk2c21.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: Vinka VK2C21 Segment LCD Controller
|
||||
+
|
||||
+maintainers:
|
||||
+ - Ricardo Pardini <ricardo@pardini.net>
|
||||
+
|
||||
+description: |
|
||||
+ The Vinka VK2C21 (and pin-compatible VK2C21A/VK2C21B) is a memory-mapping
|
||||
+ segment LCD controller that communicates over I2C. It can drive up to
|
||||
+ 128 segments (e.g. 32 SEG × 4 COM in 1/3 bias mode).
|
||||
+
|
||||
+ Many low-cost SBCs wire this chip to GPIO pins without I2C pull-up
|
||||
+ resistors, making the standard i2c-gpio (open-drain) driver unusable.
|
||||
+ This driver therefore bit-bangs I2C in push-pull mode directly via GPIO,
|
||||
+ requiring no external pull-ups.
|
||||
+
|
||||
+properties:
|
||||
+ compatible:
|
||||
+ const: vinka,vk2c21
|
||||
+
|
||||
+ sda-gpios:
|
||||
+ description: GPIO line used for I2C SDA (data).
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ scl-gpios:
|
||||
+ description: GPIO line used for I2C SCL (clock).
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ clock-frequency:
|
||||
+ description: Desired I2C clock frequency in Hz.
|
||||
+ default: 100000
|
||||
+ minimum: 1000
|
||||
+ maximum: 400000
|
||||
+
|
||||
+required:
|
||||
+ - compatible
|
||||
+ - sda-gpios
|
||||
+ - scl-gpios
|
||||
+
|
||||
+additionalProperties: false
|
||||
+
|
||||
+examples:
|
||||
+ - |
|
||||
+ #include <dt-bindings/gpio/gpio.h>
|
||||
+ #include <dt-bindings/pinctrl/rockchip.h>
|
||||
+
|
||||
+ / {
|
||||
+ segment-lcd {
|
||||
+ compatible = "vinka,vk2c21";
|
||||
+ sda-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>;
|
||||
+ scl-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>;
|
||||
+ clock-frequency = <100000>;
|
||||
+ };
|
||||
+ };
|
||||
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
|
||||
index bedc6133f970a..bc37fca63bcc9 100644
|
||||
--- a/drivers/auxdisplay/Kconfig
|
||||
+++ b/drivers/auxdisplay/Kconfig
|
||||
@@ -526,6 +526,19 @@ config SEG_LED_GPIO
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called seg-led-gpio.
|
||||
|
||||
+config LCD_VK2C21
|
||||
+ tristate "Vinka VK2C21 segment LCD controller"
|
||||
+ depends on GPIOLIB
|
||||
+ depends on OF
|
||||
+ help
|
||||
+ Say Y or M here to add support for the Vinka VK2C21 segment LCD
|
||||
+ controller found on various Rockchip-based SBCs (e.g. Mekotronics
|
||||
+ R58X). The chip communicates via I2C but is often wired without
|
||||
+ pull-up resistors, so this driver bit-bangs I2C in push-pull mode
|
||||
+ directly via GPIO.
|
||||
+
|
||||
+ The display text is controlled via sysfs.
|
||||
+
|
||||
#
|
||||
# Character LCD with non-conforming interface section
|
||||
#
|
||||
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
|
||||
index f5c13ed1cd4f5..28f6c587e1caa 100644
|
||||
--- a/drivers/auxdisplay/Makefile
|
||||
+++ b/drivers/auxdisplay/Makefile
|
||||
@@ -16,3 +16,4 @@ obj-$(CONFIG_LINEDISP) += line-display.o
|
||||
obj-$(CONFIG_MAX6959) += max6959.o
|
||||
obj-$(CONFIG_PARPORT_PANEL) += panel.o
|
||||
obj-$(CONFIG_SEG_LED_GPIO) += seg-led-gpio.o
|
||||
+obj-$(CONFIG_LCD_VK2C21) += lcd-vk2c21.o
|
||||
diff --git a/drivers/auxdisplay/lcd-vk2c21.c b/drivers/auxdisplay/lcd-vk2c21.c
|
||||
new file mode 100644
|
||||
index 0000000000000..dfc57a7534ca6
|
||||
--- /dev/null
|
||||
+++ b/drivers/auxdisplay/lcd-vk2c21.c
|
||||
@@ -0,0 +1,517 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
+/*
|
||||
+ * Driver for Vinka VK2C21 segment LCD controller
|
||||
+ *
|
||||
+ * The VK2C21 is accessed via I2C but many boards (especially Rockchip-based
|
||||
+ * SBCs) wire it without pull-up resistors, so the standard i2c-gpio driver
|
||||
+ * (open-drain) cannot communicate with it. This driver therefore bit-bangs
|
||||
+ * I2C in push-pull mode directly via GPIO.
|
||||
+ *
|
||||
+ * Datasheet: https://www.lcsc.com/datasheet/lcsc_datasheet_2302080930_VINKA-VK2C21A_C5358619.pdf
|
||||
+ *
|
||||
+ * Copyright (C) 2025 Ricardo Pardini <ricardo@pardini.net>
|
||||
+ */
|
||||
+
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/string.h>
|
||||
+
|
||||
+/*
|
||||
+ * VK2C21 I2C address. The chip has a single fixed address whose 8-bit
|
||||
+ * write form is 0x70 (7-bit: 0x38). We bit-bang the 8-bit value directly.
|
||||
+ */
|
||||
+#define VK2C21_ADDR_W 0x70
|
||||
+
|
||||
+/* VK2C21 command bytes (directly from the datasheet) */
|
||||
+#define VK2C21_RWRAM 0x80
|
||||
+#define VK2C21_MODESET 0x82
|
||||
+#define VK2C21_SYSSET 0x84
|
||||
+#define VK2C21_FRAMESET 0x86
|
||||
+#define VK2C21_BLINKSET 0x88
|
||||
+#define VK2C21_IVASET 0x8A
|
||||
+
|
||||
+/* Register values */
|
||||
+#define VK2C21_BIAS_1_3_4COM 0x00 /* 1/3 bias, 4 COM */
|
||||
+#define VK2C21_SYS_ON_LCD_ON 0x03 /* System + LCD on */
|
||||
+#define VK2C21_SYS_OFF_LCD_OFF 0x00 /* System + LCD off */
|
||||
+#define VK2C21_FRAME_80HZ 0x00 /* 80 Hz frame rate */
|
||||
+#define VK2C21_BLINK_OFF 0x00 /* Blinking off */
|
||||
+#define VK2C21_IVA_DEFAULT 0x0F /* VLCD selected, IVA off, R1 */
|
||||
+
|
||||
+/* Display RAM has 10 addressable bytes (SEG/COM matrix) */
|
||||
+#define VK2C21_RAM_SIZE 10
|
||||
+
|
||||
+/* We support a 6-digit display */
|
||||
+#define VK2C21_MAX_DIGITS 6
|
||||
+
|
||||
+/*
|
||||
+ * Segment encoding table.
|
||||
+ * Maps digit positions (0..5, left to right) to VK2C21 RAM addresses.
|
||||
+ * This table is board-specific; the values below match the common Mekotronics
|
||||
+ * and similar Rockchip SBC wiring.
|
||||
+ */
|
||||
+static const u8 vk2c21_digit_to_ram[] = { 8, 7, 6, 5, 4, 3 };
|
||||
+
|
||||
+/*
|
||||
+ * 7-segment bit mapping for this hardware:
|
||||
+ *
|
||||
+ * ─── a ───
|
||||
+ * │ │ bit 7 = a (top)
|
||||
+ * f b bit 6 = f (upper-left)
|
||||
+ * │ │ bit 5 = e (lower-left)
|
||||
+ * ─── g ─── bit 4 = d (bottom)
|
||||
+ * │ │ bit 2 = b (upper-right)
|
||||
+ * e c bit 1 = g (middle)
|
||||
+ * │ │ bit 0 = c (lower-right)
|
||||
+ * ─── d ─── bit 3 = unused (DP on some boards)
|
||||
+ */
|
||||
+#define SEG_A 0x80
|
||||
+#define SEG_B 0x04
|
||||
+#define SEG_C 0x01
|
||||
+#define SEG_D 0x10
|
||||
+#define SEG_E 0x20
|
||||
+#define SEG_F 0x40
|
||||
+#define SEG_G 0x02
|
||||
+
|
||||
+#define VK2C21_FONT_BLANK 0x00
|
||||
+
|
||||
+/*
|
||||
+ * Full 7-segment ASCII font table.
|
||||
+ * 0x00 = character cannot be meaningfully represented → blank.
|
||||
+ * Lowercase letters use the conventional 7-segment representations.
|
||||
+ *
|
||||
+ * Characters that are indistinguishable from digits or other letters
|
||||
+ * on a 7-segment display (e.g. 'S' vs '5', 'Z' vs '2') are still
|
||||
+ * included so that arbitrary text can be displayed.
|
||||
+ */
|
||||
+static const u8 vk2c21_ascii_font[128] = {
|
||||
+ /* 0x20 ' ' */ [' '] = VK2C21_FONT_BLANK,
|
||||
+ /* 0x21 '!' */ ['!'] = SEG_B | SEG_C, /* same as 1 */
|
||||
+ /* 0x22 '"' */ ['"'] = SEG_B | SEG_F,
|
||||
+ /* 0x27 ''' */ ['\''] = SEG_F,
|
||||
+ /* 0x28 '(' */ ['('] = SEG_A | SEG_D | SEG_E | SEG_F, /* same as [ */
|
||||
+ /* 0x29 ')' */ [')'] = SEG_A | SEG_B | SEG_C | SEG_D, /* same as ] */
|
||||
+ /* 0x2c ',' */ [','] = SEG_C,
|
||||
+ /* 0x2d '-' */ ['-'] = SEG_G,
|
||||
+ /* 0x2f '/' */ ['/'] = SEG_B | SEG_E | SEG_G,
|
||||
+ /* 0x30-0x39: digits */
|
||||
+ ['0'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,
|
||||
+ ['1'] = SEG_B | SEG_C,
|
||||
+ ['2'] = SEG_A | SEG_B | SEG_D | SEG_E | SEG_G,
|
||||
+ ['3'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_G,
|
||||
+ ['4'] = SEG_B | SEG_C | SEG_F | SEG_G,
|
||||
+ ['5'] = SEG_A | SEG_C | SEG_D | SEG_F | SEG_G,
|
||||
+ ['6'] = SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['7'] = SEG_A | SEG_B | SEG_C,
|
||||
+ ['8'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['9'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G,
|
||||
+ /* 0x3d '=' */ ['='] = SEG_D | SEG_G,
|
||||
+ /* 0x3f '?' */ ['?'] = SEG_A | SEG_B | SEG_E | SEG_G,
|
||||
+ /* Uppercase letters */
|
||||
+ ['A'] = SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['B'] = SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, /* same as b */
|
||||
+ ['C'] = SEG_A | SEG_D | SEG_E | SEG_F,
|
||||
+ ['D'] = SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, /* same as d */
|
||||
+ ['E'] = SEG_A | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['F'] = SEG_A | SEG_E | SEG_F | SEG_G,
|
||||
+ ['G'] = SEG_A | SEG_C | SEG_D | SEG_E | SEG_F,
|
||||
+ ['H'] = SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['I'] = SEG_E | SEG_F,
|
||||
+ ['J'] = SEG_B | SEG_C | SEG_D | SEG_E,
|
||||
+ /* K: not cleanly representable, approximate as H */
|
||||
+ ['K'] = SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['L'] = SEG_D | SEG_E | SEG_F,
|
||||
+ /* M: not representable on 7-seg, skip (will show blank) */
|
||||
+ ['N'] = SEG_C | SEG_E | SEG_G, /* same as n */
|
||||
+ ['O'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, /* same as 0 */
|
||||
+ ['P'] = SEG_A | SEG_B | SEG_E | SEG_F | SEG_G,
|
||||
+ ['Q'] = SEG_A | SEG_B | SEG_C | SEG_F | SEG_G,
|
||||
+ ['R'] = SEG_E | SEG_G, /* same as r */
|
||||
+ ['S'] = SEG_A | SEG_C | SEG_D | SEG_F | SEG_G, /* same as 5 */
|
||||
+ ['T'] = SEG_D | SEG_E | SEG_F | SEG_G, /* same as t */
|
||||
+ ['U'] = SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,
|
||||
+ ['V'] = SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, /* same as U */
|
||||
+ /* W: not representable on 7-seg, skip */
|
||||
+ /* X: not cleanly representable, approximate as H */
|
||||
+ ['X'] = SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['Y'] = SEG_B | SEG_C | SEG_D | SEG_F | SEG_G,
|
||||
+ ['Z'] = SEG_A | SEG_B | SEG_D | SEG_E | SEG_G, /* same as 2 */
|
||||
+ ['['] = SEG_A | SEG_D | SEG_E | SEG_F,
|
||||
+ [']'] = SEG_A | SEG_B | SEG_C | SEG_D,
|
||||
+ ['_'] = SEG_D,
|
||||
+ /* Lowercase letters */
|
||||
+ ['a'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,
|
||||
+ ['b'] = SEG_C | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['c'] = SEG_D | SEG_E | SEG_G,
|
||||
+ ['d'] = SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,
|
||||
+ ['e'] = SEG_A | SEG_B | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['f'] = SEG_A | SEG_E | SEG_F | SEG_G, /* same as F */
|
||||
+ ['g'] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, /* same as 9 */
|
||||
+ ['h'] = SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['i'] = SEG_C,
|
||||
+ ['j'] = SEG_B | SEG_C | SEG_D,
|
||||
+ /* k: approximate as h */
|
||||
+ ['k'] = SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['l'] = SEG_E | SEG_F,
|
||||
+ /* m: not representable on 7-seg, skip */
|
||||
+ ['n'] = SEG_C | SEG_E | SEG_G,
|
||||
+ ['o'] = SEG_C | SEG_D | SEG_E | SEG_G,
|
||||
+ ['p'] = SEG_A | SEG_B | SEG_E | SEG_F | SEG_G, /* same as P */
|
||||
+ ['q'] = SEG_A | SEG_B | SEG_C | SEG_F | SEG_G, /* same as Q */
|
||||
+ ['r'] = SEG_E | SEG_G,
|
||||
+ ['s'] = SEG_A | SEG_C | SEG_D | SEG_F | SEG_G, /* same as 5 */
|
||||
+ ['t'] = SEG_D | SEG_E | SEG_F | SEG_G,
|
||||
+ ['u'] = SEG_C | SEG_D | SEG_E,
|
||||
+ ['v'] = SEG_C | SEG_D | SEG_E, /* same as u */
|
||||
+ /* w: not representable */
|
||||
+ /* x: approximate as H */
|
||||
+ ['x'] = SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,
|
||||
+ ['y'] = SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, /* same as Y */
|
||||
+ ['z'] = SEG_A | SEG_B | SEG_D | SEG_E | SEG_G, /* same as 2 */
|
||||
+ /* 0x7c '|' */ ['|'] = SEG_E | SEG_F,
|
||||
+ /* 0x7e '~' */ ['~'] = SEG_A,
|
||||
+};
|
||||
+
|
||||
+struct vk2c21_data {
|
||||
+ struct device *dev;
|
||||
+ struct gpio_desc *sda;
|
||||
+ struct gpio_desc *scl;
|
||||
+ struct mutex lock; /* serialises I2C access */
|
||||
+ unsigned int half_period_ns;
|
||||
+ u8 dispram[VK2C21_RAM_SIZE];
|
||||
+ char display_text[VK2C21_MAX_DIGITS + 1];
|
||||
+};
|
||||
+
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+/* Low-level push-pull bit-bang I2C */
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+
|
||||
+static inline void sda_high(struct vk2c21_data *d)
|
||||
+{
|
||||
+ gpiod_direction_output(d->sda, 1);
|
||||
+}
|
||||
+
|
||||
+static inline void sda_low(struct vk2c21_data *d)
|
||||
+{
|
||||
+ gpiod_direction_output(d->sda, 0);
|
||||
+}
|
||||
+
|
||||
+static inline void scl_high(struct vk2c21_data *d)
|
||||
+{
|
||||
+ gpiod_set_value(d->scl, 1);
|
||||
+}
|
||||
+
|
||||
+static inline void scl_low(struct vk2c21_data *d)
|
||||
+{
|
||||
+ gpiod_set_value(d->scl, 0);
|
||||
+}
|
||||
+
|
||||
+static inline int sda_read(struct vk2c21_data *d)
|
||||
+{
|
||||
+ gpiod_direction_input(d->sda);
|
||||
+ return gpiod_get_value(d->sda);
|
||||
+}
|
||||
+
|
||||
+static inline void bb_delay(struct vk2c21_data *d)
|
||||
+{
|
||||
+ ndelay(d->half_period_ns);
|
||||
+}
|
||||
+
|
||||
+static void bb_start(struct vk2c21_data *d)
|
||||
+{
|
||||
+ scl_high(d);
|
||||
+ sda_high(d);
|
||||
+ bb_delay(d);
|
||||
+ sda_low(d);
|
||||
+ bb_delay(d);
|
||||
+}
|
||||
+
|
||||
+static void bb_stop(struct vk2c21_data *d)
|
||||
+{
|
||||
+ scl_high(d);
|
||||
+ sda_low(d);
|
||||
+ bb_delay(d);
|
||||
+ sda_high(d);
|
||||
+ bb_delay(d);
|
||||
+}
|
||||
+
|
||||
+/* Write one byte MSB-first; returns 0 on ACK, 1 on NACK */
|
||||
+static int bb_write_byte(struct vk2c21_data *d, u8 byte)
|
||||
+{
|
||||
+ int i, ack;
|
||||
+
|
||||
+ for (i = 7; i >= 0; i--) {
|
||||
+ scl_low(d);
|
||||
+ if (byte & BIT(i))
|
||||
+ sda_high(d);
|
||||
+ else
|
||||
+ sda_low(d);
|
||||
+ bb_delay(d);
|
||||
+ scl_high(d);
|
||||
+ bb_delay(d);
|
||||
+ }
|
||||
+
|
||||
+ /* Clock the ACK/NACK bit */
|
||||
+ scl_low(d);
|
||||
+ ack = sda_read(d);
|
||||
+ bb_delay(d);
|
||||
+ scl_high(d);
|
||||
+ bb_delay(d);
|
||||
+ scl_low(d);
|
||||
+ sda_high(d);
|
||||
+
|
||||
+ return ack;
|
||||
+}
|
||||
+
|
||||
+/* Send a two-byte command: [ADDR_W] [cmd] [data] */
|
||||
+static int vk2c21_send_cmd(struct vk2c21_data *d, u8 cmd, u8 data)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ bb_start(d);
|
||||
+ if (bb_write_byte(d, VK2C21_ADDR_W)) { ret = -EIO; goto out; }
|
||||
+ if (bb_write_byte(d, cmd)) { ret = -EIO; goto out; }
|
||||
+ if (bb_write_byte(d, data)) { ret = -EIO; goto out; }
|
||||
+out:
|
||||
+ bb_stop(d);
|
||||
+ if (ret)
|
||||
+ dev_err(d->dev, "command 0x%02x 0x%02x failed (no ACK)\n",
|
||||
+ cmd, data);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/* Write one byte to display RAM: [ADDR_W] [0x80] [addr] [data] */
|
||||
+static int vk2c21_write_ram(struct vk2c21_data *d, u8 addr, u8 data)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ bb_start(d);
|
||||
+ if (bb_write_byte(d, VK2C21_ADDR_W)) { ret = -EIO; goto out; }
|
||||
+ if (bb_write_byte(d, VK2C21_RWRAM)) { ret = -EIO; goto out; }
|
||||
+ if (bb_write_byte(d, addr)) { ret = -EIO; goto out; }
|
||||
+ if (bb_write_byte(d, data)) { ret = -EIO; goto out; }
|
||||
+out:
|
||||
+ bb_stop(d);
|
||||
+ if (ret)
|
||||
+ dev_err(d->dev, "write RAM[%u]=0x%02x failed\n", addr, data);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+/* Display helpers */
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+
|
||||
+static int vk2c21_clear(struct vk2c21_data *d)
|
||||
+{
|
||||
+ int i, ret;
|
||||
+
|
||||
+ memset(d->dispram, 0, sizeof(d->dispram));
|
||||
+ for (i = 0; i < VK2C21_RAM_SIZE; i++) {
|
||||
+ ret = vk2c21_write_ram(d, i, 0x00);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int vk2c21_init_hw(struct vk2c21_data *d)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = vk2c21_send_cmd(d, VK2C21_MODESET, VK2C21_BIAS_1_3_4COM);
|
||||
+ if (ret) return ret;
|
||||
+ ret = vk2c21_send_cmd(d, VK2C21_SYSSET, VK2C21_SYS_ON_LCD_ON);
|
||||
+ if (ret) return ret;
|
||||
+ ret = vk2c21_send_cmd(d, VK2C21_FRAMESET, VK2C21_FRAME_80HZ);
|
||||
+ if (ret) return ret;
|
||||
+ ret = vk2c21_send_cmd(d, VK2C21_BLINKSET, VK2C21_BLINK_OFF);
|
||||
+ if (ret) return ret;
|
||||
+ ret = vk2c21_send_cmd(d, VK2C21_IVASET, VK2C21_IVA_DEFAULT);
|
||||
+ if (ret) return ret;
|
||||
+
|
||||
+ return vk2c21_clear(d);
|
||||
+}
|
||||
+
|
||||
+static u8 vk2c21_char_to_glyph(char c)
|
||||
+{
|
||||
+ if (c >= 0 && c < 128)
|
||||
+ return vk2c21_ascii_font[(unsigned char)c];
|
||||
+ return VK2C21_FONT_BLANK;
|
||||
+}
|
||||
+
|
||||
+static int vk2c21_display_string(struct vk2c21_data *d, const char *str)
|
||||
+{
|
||||
+ int i, ret, len;
|
||||
+
|
||||
+ len = strlen(str);
|
||||
+ for (i = 0; i < VK2C21_MAX_DIGITS; i++) {
|
||||
+ char c = (i < len) ? str[i] : ' ';
|
||||
+ u8 glyph = vk2c21_char_to_glyph(c);
|
||||
+
|
||||
+ ret = vk2c21_write_ram(d, vk2c21_digit_to_ram[i], glyph);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+/* sysfs interface */
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+
|
||||
+static ssize_t display_show(struct device *dev,
|
||||
+ struct device_attribute *attr, char *buf)
|
||||
+{
|
||||
+ struct vk2c21_data *d = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return sysfs_emit(buf, "%s\n", d->display_text);
|
||||
+}
|
||||
+
|
||||
+static ssize_t display_store(struct device *dev,
|
||||
+ struct device_attribute *attr,
|
||||
+ const char *buf, size_t count)
|
||||
+{
|
||||
+ struct vk2c21_data *d = dev_get_drvdata(dev);
|
||||
+ char text[VK2C21_MAX_DIGITS + 1] = {};
|
||||
+ int ret, len;
|
||||
+
|
||||
+ len = min_t(int, count, VK2C21_MAX_DIGITS);
|
||||
+ if (len > 0 && buf[len - 1] == '\n')
|
||||
+ len--;
|
||||
+ memcpy(text, buf, len);
|
||||
+
|
||||
+ mutex_lock(&d->lock);
|
||||
+ ret = vk2c21_display_string(d, text);
|
||||
+ if (!ret) {
|
||||
+ memcpy(d->display_text, text, len);
|
||||
+ d->display_text[len] = '\0';
|
||||
+ }
|
||||
+ mutex_unlock(&d->lock);
|
||||
+
|
||||
+ return ret ? ret : count;
|
||||
+}
|
||||
+static DEVICE_ATTR_RW(display);
|
||||
+
|
||||
+static ssize_t clear_store(struct device *dev,
|
||||
+ struct device_attribute *attr,
|
||||
+ const char *buf, size_t count)
|
||||
+{
|
||||
+ struct vk2c21_data *d = dev_get_drvdata(dev);
|
||||
+ int ret;
|
||||
+
|
||||
+ mutex_lock(&d->lock);
|
||||
+ ret = vk2c21_clear(d);
|
||||
+ if (!ret)
|
||||
+ d->display_text[0] = '\0';
|
||||
+ mutex_unlock(&d->lock);
|
||||
+
|
||||
+ return ret ? ret : count;
|
||||
+}
|
||||
+static DEVICE_ATTR_WO(clear);
|
||||
+
|
||||
+static struct attribute *vk2c21_sysfs_attrs[] = {
|
||||
+ &dev_attr_display.attr,
|
||||
+ &dev_attr_clear.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group vk2c21_sysfs_group = {
|
||||
+ .attrs = vk2c21_sysfs_attrs,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group *vk2c21_sysfs_groups[] = {
|
||||
+ &vk2c21_sysfs_group,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+/* Platform driver */
|
||||
+/* ------------------------------------------------------------------ */
|
||||
+
|
||||
+static int vk2c21_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct vk2c21_data *d;
|
||||
+ u32 freq = 100000;
|
||||
+ int ret;
|
||||
+
|
||||
+ d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
|
||||
+ if (!d)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ d->dev = dev;
|
||||
+ mutex_init(&d->lock);
|
||||
+ platform_set_drvdata(pdev, d);
|
||||
+
|
||||
+ d->sda = devm_gpiod_get(dev, "sda", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(d->sda))
|
||||
+ return dev_err_probe(dev, PTR_ERR(d->sda),
|
||||
+ "failed to get SDA GPIO\n");
|
||||
+
|
||||
+ d->scl = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH);
|
||||
+ if (IS_ERR(d->scl))
|
||||
+ return dev_err_probe(dev, PTR_ERR(d->scl),
|
||||
+ "failed to get SCL GPIO\n");
|
||||
+
|
||||
+ of_property_read_u32(dev->of_node, "clock-frequency", &freq);
|
||||
+ if (!freq)
|
||||
+ freq = 100000;
|
||||
+ d->half_period_ns = 500000000U / freq;
|
||||
+
|
||||
+ dev_info(dev, "I2C bitbang @ ~%u Hz, half-period %u ns\n",
|
||||
+ freq, d->half_period_ns);
|
||||
+
|
||||
+ mutex_lock(&d->lock);
|
||||
+ ret = vk2c21_init_hw(d);
|
||||
+ mutex_unlock(&d->lock);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "hardware init failed\n");
|
||||
+
|
||||
+ dev_info(dev, "VK2C21 segment LCD ready\n");
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void vk2c21_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct vk2c21_data *d = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ mutex_lock(&d->lock);
|
||||
+ vk2c21_send_cmd(d, VK2C21_SYSSET, VK2C21_SYS_OFF_LCD_OFF);
|
||||
+ mutex_unlock(&d->lock);
|
||||
+}
|
||||
+
|
||||
+static void vk2c21_shutdown(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct vk2c21_data *d = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ mutex_lock(&d->lock);
|
||||
+ vk2c21_send_cmd(d, VK2C21_SYSSET, VK2C21_SYS_OFF_LCD_OFF);
|
||||
+ mutex_unlock(&d->lock);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id vk2c21_of_match[] = {
|
||||
+ { .compatible = "vinka,vk2c21" },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, vk2c21_of_match);
|
||||
+
|
||||
+static struct platform_driver vk2c21_driver = {
|
||||
+ .probe = vk2c21_probe,
|
||||
+ .remove = vk2c21_remove,
|
||||
+ .shutdown = vk2c21_shutdown,
|
||||
+ .driver = {
|
||||
+ .name = "lcd-vk2c21",
|
||||
+ .of_match_table = vk2c21_of_match,
|
||||
+ .dev_groups = vk2c21_sysfs_groups,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(vk2c21_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Ricardo Pardini <ricardo@pardini.net>");
|
||||
+MODULE_DESCRIPTION("Vinka VK2C21 segment LCD controller driver (push-pull bitbang I2C)");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--
|
||||
2.53.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user