Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+551
View File
@@ -0,0 +1,551 @@
# SPDX-License-Identifier: GPL-2.0
#
# For a description of the syntax of this configuration file,
# see Documentation/kbuild/kconfig-language.rst.
#
# Auxiliary display drivers configuration.
#
menuconfig AUXDISPLAY
bool "Auxiliary Display support"
help
Say Y here to get to see options for auxiliary display drivers.
This option alone does not add any kernel code.
If you say N, all options in this submenu will be skipped and disabled.
if AUXDISPLAY
#
# Character LCD section
#
config CHARLCD
tristate "Character LCD core support" if COMPILE_TEST
help
This is the base system for character-based LCD displays.
It makes no sense to have this alone, you select your display driver
and if it needs the charlcd core, it will select it automatically.
This is some character LCD core interface that multiple drivers can
use.
config HD44780_COMMON
tristate "Common functions for HD44780 (and compatibles) LCD displays" if COMPILE_TEST
select CHARLCD
help
This is a module with the common symbols for HD44780 (and compatibles)
displays. This is the code that multiple other modules use. It is not
useful alone. If you have some sort of HD44780 compatible display,
you very likely use this. It is selected automatically by selecting
your concrete display.
config HD44780
tristate "HD44780 Character LCD support"
depends on GPIOLIB || COMPILE_TEST
select HD44780_COMMON
help
Enable support for Character LCDs using a HD44780 controller.
The LCD is accessible through the /dev/lcd char device (10, 156).
This code can either be compiled as a module, or linked into the
kernel and started at boot.
If you don't understand what all this is about, say N.
config LCD2S
tristate "lcd2s 20x4 character display over I2C console"
depends on I2C
select CHARLCD
help
This is a driver that lets you use the lcd2s 20x4 character display
from Modtronix engineering as a console output device. The display
is a simple single color character display. You have to connect it
to an I2C bus.
menuconfig PARPORT_PANEL
tristate "Parallel port LCD/Keypad Panel support"
depends on PARPORT
select HD44780_COMMON
help
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
parallel port. This driver also features 4 and 6-key keypads. The LCD
is accessible through the /dev/lcd char device (10, 156), and the
keypad through /dev/keypad (10, 185). This code can either be
compiled as a module, or linked into the kernel and started at boot.
If you don't understand what all this is about, say N.
if PARPORT_PANEL
config PANEL_PARPORT
int "Default parallel port number (0=LPT1)"
range 0 255
default "0"
help
This is the index of the parallel port the panel is connected to. One
driver instance only supports one parallel port, so if your keypad
and LCD are connected to two separate ports, you have to start two
modules with different arguments. Numbering starts with '0' for LPT1,
and so on.
config PANEL_PROFILE
int "Default panel profile (0-5, 0=custom)"
range 0 5
default "5"
help
To ease configuration, the driver supports different configuration
profiles for past and recent wirings. These profiles can also be
used to define an approximative configuration, completed by a few
other options. Here are the profiles :
0 = custom (see further)
1 = 2x16 parallel LCD, old keypad
2 = 2x16 serial LCD (KS-0074), new keypad
3 = 2x16 parallel LCD (Hantronix), no keypad
4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad
5 = 2x40 parallel LCD (old one), with old keypad
Custom configurations allow you to define how your display is
wired to the parallel port, and how it works. This is only intended
for experts.
config PANEL_KEYPAD
depends on PANEL_PROFILE="0"
int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
range 0 3
default 0
help
This enables and configures a keypad connected to the parallel port.
The keys will be read from character device 10,185. Valid values are :
0 : do not enable this driver
1 : old 6 keys keypad
2 : new 6 keys keypad, as used on the server at www.ant-computing.com
3 : Nexcom NSA1045's 4 keys keypad
New profiles can be described in the driver source. The driver also
supports simultaneous keys pressed when the keypad supports them.
config PANEL_LCD
depends on PANEL_PROFILE="0"
int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
range 0 5
default 0
help
This enables and configures an LCD connected to the parallel port.
The driver includes an interpreter for escape codes starting with
'\e[L' which are specific to the LCD, and a few ANSI codes. The
driver will be registered as character device 10,156, usually
under the name '/dev/lcd'. There are a total of 6 supported types :
0 : do not enable the driver
1 : custom configuration and wiring (see further)
2 : 2x16 & 2x40 parallel LCD (old wiring)
3 : 2x16 serial LCD (KS-0074 based)
4 : 2x16 parallel LCD (Hantronix wiring)
5 : 2x16 parallel LCD (Nexcom wiring)
When type '1' is specified, other options will appear to configure
more precise aspects (wiring, dimensions, protocol, ...). Please note
that those values changed from the 2.4 driver for better consistency.
config PANEL_LCD_HEIGHT
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "Number of lines on the LCD (1-2)"
range 1 2
default 2
help
This is the number of visible character lines on the LCD in custom profile.
It can either be 1 or 2.
config PANEL_LCD_WIDTH
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "Number of characters per line on the LCD (1-40)"
range 1 40
default 40
help
This is the number of characters per line on the LCD in custom profile.
Common values are 16,20,24,40.
config PANEL_LCD_BWIDTH
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "Internal LCD line width (1-40, 40 by default)"
range 1 40
default 40
help
Most LCDs use a standard controller which supports hardware lines of 40
characters, although sometimes only 16, 20 or 24 of them are really wired
to the terminal. This results in some non-visible but addressable characters,
and is the case for most parallel LCDs. Other LCDs, and some serial ones,
however, use the same line width internally as what is visible. The KS0074
for example, uses 16 characters per line for 16 visible characters per line.
This option lets you configure the value used by your LCD in 'custom' profile.
If you don't know, put '40' here.
config PANEL_LCD_HWIDTH
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "Hardware LCD line width (1-64, 64 by default)"
range 1 64
default 64
help
Most LCDs use a single address bit to differentiate line 0 and line 1. Since
some of them need to be able to address 40 chars with the lower bits, they
often use the immediately superior power of 2, which is 64, to address the
next line.
If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and
64 here for a 2x40.
config PANEL_LCD_CHARSET
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "LCD character set (0=normal, 1=KS0074)"
range 0 1
default 0
help
Some controllers such as the KS0074 use a somewhat strange character set
where many symbols are at unusual places. The driver knows how to map
'standard' ASCII characters to the character sets used by these controllers.
Valid values are :
0 : normal (untranslated) character set
1 : KS0074 character set
If you don't know, use the normal one (0).
config PANEL_LCD_PROTO
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "LCD communication mode (0=parallel 8 bits, 1=serial)"
range 0 1
default 0
help
This driver now supports any serial or parallel LCD wired to a parallel
port. But before assigning signals, the driver needs to know if it will
be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires
(SDA/SCL), while parallel ones use 2 or 3 wires for the control signals
(E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits
parallel LCD, and 1 for a serial LCD.
config PANEL_LCD_PIN_E
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
range -17 17
default 14
help
This describes the number of the parallel port pin to which the LCD 'E'
signal has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'E' pin in custom profile is '14' (AUTOFEED).
config PANEL_LCD_PIN_RS
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
range -17 17
default 17
help
This describes the number of the parallel port pin to which the LCD 'RS'
signal has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'RS' pin in custom profile is '17' (SELECT IN).
config PANEL_LCD_PIN_RW
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
range -17 17
default 16
help
This describes the number of the parallel port pin to which the LCD 'RW'
signal has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'RW' pin in custom profile is '16' (INIT).
config PANEL_LCD_PIN_SCL
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
range -17 17
default 1
help
This describes the number of the parallel port pin to which the serial
LCD 'SCL' signal has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'SCL' pin in custom profile is '1' (STROBE).
config PANEL_LCD_PIN_SDA
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
range -17 17
default 2
help
This describes the number of the parallel port pin to which the serial
LCD 'SDA' signal has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'SDA' pin in custom profile is '2' (D0).
config PANEL_LCD_PIN_BL
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
range -17 17
default 0
help
This describes the number of the parallel port pin to which the LCD 'BL' signal
has been connected. It can be :
0 : no connection (eg: connected to ground)
1..17 : directly connected to any of these pins on the DB25 plug
-1..-17 : connected to the same pin through an inverter (eg: transistor).
Default for the 'BL' pin in custom profile is '0' (uncontrolled).
endif # PARPORT_PANEL
config PANEL_CHANGE_MESSAGE
bool "Change LCD initialization message ?"
depends on CHARLCD || LINEDISP
help
This allows you to replace the boot message indicating the kernel version
and the driver version with a custom message. This is useful on appliances
where a simple 'Starting system' message can be enough to stop a customer
from worrying.
If you say 'Y' here, you'll be able to choose a message yourself. Otherwise,
say 'N' and keep the default message with the version.
config PANEL_BOOT_MESSAGE
depends on PANEL_CHANGE_MESSAGE
string "New initialization message"
default ""
help
This allows you to replace the boot message indicating the kernel version
and the driver version with a custom message. This is useful on appliances
where a simple 'Starting system' message can be enough to stop a customer
from worrying.
An empty message will only clear the display at driver init time. Any other
printf()-formatted message is valid with newline and escape codes.
choice
prompt "Backlight initial state"
default CHARLCD_BL_FLASH
help
Select the initial backlight state on boot or module load.
Previously, there was no option for this: the backlight flashed
briefly on init. Now you can also turn it off/on.
config CHARLCD_BL_OFF
bool "Off"
help
Backlight is initially turned off
config CHARLCD_BL_ON
bool "On"
help
Backlight is initially turned on
config CHARLCD_BL_FLASH
bool "Flash"
help
Backlight is flashed briefly on init
endchoice
#
# Samsung KS0108 LCD controller section
#
config KS0108
tristate "KS0108 LCD Controller"
depends on PARPORT_PC
help
If you have a LCD controlled by one or more KS0108
controllers, say Y. You will need also another more specific
driver for your LCD.
Depends on Parallel Port support. If you say Y at
parport, you will be able to compile this as a module (M)
and built-in as well (Y).
To compile this as a module, choose M here:
the module will be called ks0108.
If unsure, say N.
config KS0108_PORT
hex "Parallel port where the LCD is connected"
depends on KS0108
default 0x378
help
The address of the parallel port where the LCD is connected.
The first standard parallel port address is 0x378.
The second standard parallel port address is 0x278.
The third standard parallel port address is 0x3BC.
You can specify a different address if you need.
If you don't know what I'm talking about, load the parport module,
and execute "dmesg" or "cat /proc/ioports". You can see there how
many parallel ports are present and which address each one has.
Usually you only need to use 0x378.
If you compile this as a module, you can still override this
using the module parameters.
config KS0108_DELAY
int "Delay between each control writing (microseconds)"
depends on KS0108
default "2"
help
Amount of time the ks0108 should wait between each control write
to the parallel port.
If your LCD seems to miss random writings, increment this.
If you don't know what I'm talking about, ignore it.
If you compile this as a module, you can still override this
value using the module parameters.
config CFAG12864B
tristate "CFAG12864B LCD"
depends on X86
depends on FB
depends on KS0108
select FB_SYSMEM_HELPERS
help
If you have a Crystalfontz 128x64 2-color LCD, cfag12864b Series,
say Y. You also need the ks0108 LCD Controller driver.
For help about how to wire your LCD to the parallel port,
check Documentation/admin-guide/auxdisplay/cfag12864b.rst
Depends on the x86 arch and the framebuffer support.
The LCD framebuffer driver can be attached to a console.
It will work fine. However, you can't attach it to the fbdev driver
of the xorg server.
To compile this as a module, choose M here:
the modules will be called cfag12864b and cfag12864bfb.
If unsure, say N.
config CFAG12864B_RATE
int "Refresh rate (hertz)"
depends on CFAG12864B
default "20"
help
Refresh rate of the LCD.
As the LCD is not memory mapped, the driver has to make the work by
software. This means you should be careful setting this value higher.
If your CPUs are really slow or you feel the system is slowed down,
decrease the value.
Be careful modifying this value to a very high value:
You can freeze the computer, or the LCD maybe can't draw as fast as you
are requesting.
If you don't know what I'm talking about, ignore it.
If you compile this as a module, you can still override this
value using the module parameters.
#
# Single character line display section
#
config LINEDISP
tristate "Character line display core support" if COMPILE_TEST
help
This is the core support for single-line character displays, to be
selected by drivers that use it.
config IMG_ASCII_LCD
tristate "Imagination Technologies ASCII LCD Display"
depends on HAS_IOMEM
default y if MIPS_MALTA
select MFD_SYSCON
select LINEDISP
help
Enable this to support the simple ASCII LCD displays found on
development boards such as the MIPS Boston, MIPS Malta & MIPS SEAD3
from Imagination Technologies.
config HT16K33
tristate "Holtek Ht16K33 LED controller with keyscan"
depends on FB && I2C && INPUT && BACKLIGHT_CLASS_DEVICE
select FB_SYSMEM_HELPERS
select INPUT_MATRIXKMAP
select FB_BACKLIGHT
select NEW_LEDS
select LEDS_CLASS
select LINEDISP
help
Say yes here to add support for Holtek HT16K33, RAM mapping 16*8
LED controller driver with keyscan.
config MAX6959
tristate "Maxim MAX6958/6959 7-segment LED controller"
depends on I2C
select BITREVERSE
select REGMAP_I2C
select LINEDISP
help
If you say yes here you get support for the following Maxim chips
(I2C 7-segment LED display controller):
- MAX6958
- MAX6959 (input support)
This driver can also be built as a module. If so, the module
will be called max6959.
config SEG_LED_GPIO
tristate "Generic 7-segment LED display"
depends on GPIOLIB || COMPILE_TEST
select LINEDISP
help
This driver supports a generic 7-segment LED display made up
of GPIO pins connected to the individual segments.
This driver can also be built as a module. If so, the module
will be called seg-led-gpio.
#
# Character LCD with non-conforming interface section
#
config ARM_CHARLCD
bool "ARM Ltd. Character LCD Driver"
depends on PLAT_VERSATILE
help
This is a driver for the character LCD found on the ARM Ltd.
Versatile and RealView Platform Baseboards. It doesn't do
very much more than display the text "ARM Linux" on the first
line and the Linux version on the second line, but that's
still useful.
endif # AUXDISPLAY
#
# Deprecated options
#
config PANEL
tristate "Parallel port LCD/Keypad Panel support (OLD OPTION)"
depends on PARPORT
select AUXDISPLAY
select PARPORT_PANEL
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the kernel auxiliary displays device drivers.
#
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
obj-$(CONFIG_CHARLCD) += charlcd.o
obj-$(CONFIG_HD44780_COMMON) += hd44780_common.o
obj-$(CONFIG_HD44780) += hd44780.o
obj-$(CONFIG_HT16K33) += ht16k33.o
obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
obj-$(CONFIG_KS0108) += ks0108.o
obj-$(CONFIG_LCD2S) += lcd2s.o
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
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the on-board character LCD found on some ARM reference boards
* This is basically an Hitachi HD44780 LCD with a custom IP block to drive it
* https://en.wikipedia.org/wiki/HD44780_Character_LCD
* Currently it will just display the text "ARM Linux" and the linux version
*
* Author: Linus Walleij <triad@df.lth.se>
*/
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <generated/utsrelease.h>
#define DRIVERNAME "arm-charlcd"
#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000))
/* Offsets to registers */
#define CHAR_COM 0x00U
#define CHAR_DAT 0x04U
#define CHAR_RD 0x08U
#define CHAR_RAW 0x0CU
#define CHAR_MASK 0x10U
#define CHAR_STAT 0x14U
#define CHAR_RAW_CLEAR 0x00000000U
#define CHAR_RAW_VALID 0x00000100U
/* Hitachi HD44780 display commands */
#define HD_CLEAR 0x01U
#define HD_HOME 0x02U
#define HD_ENTRYMODE 0x04U
#define HD_ENTRYMODE_INCREMENT 0x02U
#define HD_ENTRYMODE_SHIFT 0x01U
#define HD_DISPCTRL 0x08U
#define HD_DISPCTRL_ON 0x04U
#define HD_DISPCTRL_CURSOR_ON 0x02U
#define HD_DISPCTRL_CURSOR_BLINK 0x01U
#define HD_CRSR_SHIFT 0x10U
#define HD_CRSR_SHIFT_DISPLAY 0x08U
#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U
#define HD_FUNCSET 0x20U
#define HD_FUNCSET_8BIT 0x10U
#define HD_FUNCSET_2_LINES 0x08U
#define HD_FUNCSET_FONT_5X10 0x04U
#define HD_SET_CGRAM 0x40U
#define HD_SET_DDRAM 0x80U
#define HD_BUSY_FLAG 0x80U
/**
* struct charlcd - Private data structure
* @dev: a pointer back to containing device
* @virtbase: the offset to the controller in virtual memory
* @irq: reserved interrupt number
* @complete: completion structure for the last LCD command
* @init_work: delayed work structure to initialize the display on boot
*/
struct charlcd {
struct device *dev;
void __iomem *virtbase;
int irq;
struct completion complete;
struct delayed_work init_work;
};
static irqreturn_t charlcd_interrupt(int irq, void *data)
{
struct charlcd *lcd = data;
u8 status;
status = readl(lcd->virtbase + CHAR_STAT) & 0x01;
/* Clear IRQ */
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
if (status)
complete(&lcd->complete);
else
dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status);
return IRQ_HANDLED;
}
static void charlcd_wait_complete_irq(struct charlcd *lcd)
{
int ret;
ret = wait_for_completion_interruptible_timeout(&lcd->complete,
CHARLCD_TIMEOUT);
/* Disable IRQ after completion */
writel(0x00, lcd->virtbase + CHAR_MASK);
if (ret < 0) {
dev_err(lcd->dev,
"wait_for_completion_interruptible_timeout() returned %d waiting for ready\n",
ret);
return;
}
if (ret == 0) {
dev_err(lcd->dev, "charlcd controller timed out waiting for ready\n");
return;
}
}
static u8 charlcd_4bit_read_char(struct charlcd *lcd)
{
u8 data;
u32 val;
/* If we can, use an IRQ to wait for the data, else poll */
if (lcd->irq >= 0)
charlcd_wait_complete_irq(lcd);
else {
udelay(100);
readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
val & CHAR_RAW_VALID, 100, 1000);
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
}
msleep(1);
/* Read the 4 high bits of the data */
data = readl(lcd->virtbase + CHAR_RD) & 0xf0;
/*
* The second read for the low bits does not trigger an IRQ
* so in this case we have to poll for the 4 lower bits
*/
udelay(100);
readl_poll_timeout_atomic(lcd->virtbase + CHAR_RAW, val,
val & CHAR_RAW_VALID, 100, 1000);
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
msleep(1);
/* Read the 4 low bits of the data */
data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f;
return data;
}
static bool charlcd_4bit_read_bf(struct charlcd *lcd)
{
if (lcd->irq >= 0) {
/*
* If we'll use IRQs to wait for the busyflag, clear any
* pending flag and enable IRQ
*/
writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
init_completion(&lcd->complete);
writel(0x01, lcd->virtbase + CHAR_MASK);
}
readl(lcd->virtbase + CHAR_COM);
return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG;
}
static void charlcd_4bit_wait_busy(struct charlcd *lcd)
{
int retries = 50;
udelay(100);
while (charlcd_4bit_read_bf(lcd) && retries)
retries--;
if (!retries)
dev_err(lcd->dev, "timeout waiting for busyflag\n");
}
static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd)
{
u32 cmdlo = (cmd << 4) & 0xf0;
u32 cmdhi = (cmd & 0xf0);
writel(cmdhi, lcd->virtbase + CHAR_COM);
udelay(10);
writel(cmdlo, lcd->virtbase + CHAR_COM);
charlcd_4bit_wait_busy(lcd);
}
static void charlcd_4bit_char(struct charlcd *lcd, u8 ch)
{
u32 chlo = (ch << 4) & 0xf0;
u32 chhi = (ch & 0xf0);
writel(chhi, lcd->virtbase + CHAR_DAT);
udelay(10);
writel(chlo, lcd->virtbase + CHAR_DAT);
charlcd_4bit_wait_busy(lcd);
}
static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str)
{
u8 offset;
int i;
/*
* We support line 0, 1
* Line 1 runs from 0x00..0x27
* Line 2 runs from 0x28..0x4f
*/
if (line == 0)
offset = 0;
else if (line == 1)
offset = 0x28;
else
return;
/* Set offset */
charlcd_4bit_command(lcd, HD_SET_DDRAM | offset);
/* Send string */
for (i = 0; i < strlen(str) && i < 0x28; i++)
charlcd_4bit_char(lcd, str[i]);
}
static void charlcd_4bit_init(struct charlcd *lcd)
{
/* These commands cannot be checked with the busy flag */
writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
msleep(5);
writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
udelay(100);
writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
udelay(100);
/* Go to 4bit mode */
writel(HD_FUNCSET, lcd->virtbase + CHAR_COM);
udelay(100);
/*
* 4bit mode, 2 lines, 5x8 font, after this the number of lines
* and the font cannot be changed until the next initialization sequence
*/
charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES);
charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT);
charlcd_4bit_command(lcd, HD_CLEAR);
charlcd_4bit_command(lcd, HD_HOME);
/* Put something useful in the display */
charlcd_4bit_print(lcd, 0, "ARM Linux");
charlcd_4bit_print(lcd, 1, UTS_RELEASE);
}
static void charlcd_init_work(struct work_struct *work)
{
struct charlcd *lcd =
container_of(work, struct charlcd, init_work.work);
charlcd_4bit_init(lcd);
}
static int __init charlcd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
struct charlcd *lcd;
lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
lcd->dev = &pdev->dev;
lcd->virtbase = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(lcd->virtbase))
return PTR_ERR(lcd->virtbase);
lcd->irq = platform_get_irq(pdev, 0);
/* If no IRQ is supplied, we'll survive without it */
if (lcd->irq >= 0) {
ret = devm_request_irq(dev, lcd->irq, charlcd_interrupt, 0, DRIVERNAME, lcd);
if (ret)
return ret;
}
platform_set_drvdata(pdev, lcd);
/*
* Initialize the display in a delayed work, because
* it is VERY slow and would slow down the boot of the system.
*/
INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work);
schedule_delayed_work(&lcd->init_work, 0);
return 0;
}
static int charlcd_suspend(struct device *dev)
{
struct charlcd *lcd = dev_get_drvdata(dev);
/* Power the display off */
charlcd_4bit_command(lcd, HD_DISPCTRL);
return 0;
}
static int charlcd_resume(struct device *dev)
{
struct charlcd *lcd = dev_get_drvdata(dev);
/* Turn the display back on */
charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
return 0;
}
static const struct dev_pm_ops charlcd_pm_ops = {
.suspend = charlcd_suspend,
.resume = charlcd_resume,
};
static const struct of_device_id charlcd_match[] = {
{ .compatible = "arm,versatile-lcd", },
{}
};
static struct platform_driver charlcd_driver = {
.driver = {
.name = DRIVERNAME,
.pm = &charlcd_pm_ops,
.suppress_bind_attrs = true,
.of_match_table = charlcd_match,
},
};
builtin_platform_driver_probe(charlcd_driver, charlcd_probe);
+368
View File
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Filename: cfag12864b.c
* Version: 0.1.0
* Description: cfag12864b LCD driver
* Depends: ks0108
*
* Author: Copyright (C) Miguel Ojeda <ojeda@kernel.org>
* Date: 2006-10-31
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/ks0108.h>
#include <linux/cfag12864b.h>
#define CFAG12864B_NAME "cfag12864b"
/*
* Module Parameters
*/
static unsigned int cfag12864b_rate = CONFIG_CFAG12864B_RATE;
module_param(cfag12864b_rate, uint, 0444);
MODULE_PARM_DESC(cfag12864b_rate,
"Refresh rate (hertz)");
/*
* cfag12864b Commands
*
* E = Enable signal
* Every time E switch from low to high,
* cfag12864b/ks0108 reads the command/data.
*
* CS1 = First ks0108controller.
* If high, the first ks0108 controller receives commands/data.
*
* CS2 = Second ks0108 controller
* If high, the second ks0108 controller receives commands/data.
*
* DI = Data/Instruction
* If low, cfag12864b will expect commands.
* If high, cfag12864b will expect data.
*
*/
#define bit(n) (((unsigned char)1)<<(n))
#define CFAG12864B_BIT_E (0)
#define CFAG12864B_BIT_CS1 (2)
#define CFAG12864B_BIT_CS2 (1)
#define CFAG12864B_BIT_DI (3)
static unsigned char cfag12864b_state;
static void cfag12864b_set(void)
{
ks0108_writecontrol(cfag12864b_state);
}
static void cfag12864b_setbit(unsigned char state, unsigned char n)
{
if (state)
cfag12864b_state |= bit(n);
else
cfag12864b_state &= ~bit(n);
}
static void cfag12864b_e(unsigned char state)
{
cfag12864b_setbit(state, CFAG12864B_BIT_E);
cfag12864b_set();
}
static void cfag12864b_cs1(unsigned char state)
{
cfag12864b_setbit(state, CFAG12864B_BIT_CS1);
}
static void cfag12864b_cs2(unsigned char state)
{
cfag12864b_setbit(state, CFAG12864B_BIT_CS2);
}
static void cfag12864b_di(unsigned char state)
{
cfag12864b_setbit(state, CFAG12864B_BIT_DI);
}
static void cfag12864b_setcontrollers(unsigned char first,
unsigned char second)
{
if (first)
cfag12864b_cs1(0);
else
cfag12864b_cs1(1);
if (second)
cfag12864b_cs2(0);
else
cfag12864b_cs2(1);
}
static void cfag12864b_controller(unsigned char which)
{
if (which == 0)
cfag12864b_setcontrollers(1, 0);
else if (which == 1)
cfag12864b_setcontrollers(0, 1);
}
static void cfag12864b_displaystate(unsigned char state)
{
cfag12864b_di(0);
cfag12864b_e(1);
ks0108_displaystate(state);
cfag12864b_e(0);
}
static void cfag12864b_address(unsigned char address)
{
cfag12864b_di(0);
cfag12864b_e(1);
ks0108_address(address);
cfag12864b_e(0);
}
static void cfag12864b_page(unsigned char page)
{
cfag12864b_di(0);
cfag12864b_e(1);
ks0108_page(page);
cfag12864b_e(0);
}
static void cfag12864b_startline(unsigned char startline)
{
cfag12864b_di(0);
cfag12864b_e(1);
ks0108_startline(startline);
cfag12864b_e(0);
}
static void cfag12864b_writebyte(unsigned char byte)
{
cfag12864b_di(1);
cfag12864b_e(1);
ks0108_writedata(byte);
cfag12864b_e(0);
}
static void cfag12864b_nop(void)
{
cfag12864b_startline(0);
}
/*
* cfag12864b Internal Commands
*/
static void cfag12864b_on(void)
{
cfag12864b_setcontrollers(1, 1);
cfag12864b_displaystate(1);
}
static void cfag12864b_off(void)
{
cfag12864b_setcontrollers(1, 1);
cfag12864b_displaystate(0);
}
static void cfag12864b_clear(void)
{
unsigned char i, j;
cfag12864b_setcontrollers(1, 1);
for (i = 0; i < CFAG12864B_PAGES; i++) {
cfag12864b_page(i);
cfag12864b_address(0);
for (j = 0; j < CFAG12864B_ADDRESSES; j++)
cfag12864b_writebyte(0);
}
}
/*
* Update work
*/
unsigned char *cfag12864b_buffer;
static unsigned char *cfag12864b_cache;
static DEFINE_MUTEX(cfag12864b_mutex);
static unsigned char cfag12864b_updating;
static void cfag12864b_update(struct work_struct *delayed_work);
static struct workqueue_struct *cfag12864b_workqueue;
static DECLARE_DELAYED_WORK(cfag12864b_work, cfag12864b_update);
static void cfag12864b_queue(void)
{
queue_delayed_work(cfag12864b_workqueue, &cfag12864b_work,
HZ / cfag12864b_rate);
}
unsigned char cfag12864b_enable(void)
{
unsigned char ret;
mutex_lock(&cfag12864b_mutex);
if (!cfag12864b_updating) {
cfag12864b_updating = 1;
cfag12864b_queue();
ret = 0;
} else
ret = 1;
mutex_unlock(&cfag12864b_mutex);
return ret;
}
void cfag12864b_disable(void)
{
mutex_lock(&cfag12864b_mutex);
if (cfag12864b_updating) {
cfag12864b_updating = 0;
cancel_delayed_work(&cfag12864b_work);
flush_workqueue(cfag12864b_workqueue);
}
mutex_unlock(&cfag12864b_mutex);
}
static void cfag12864b_update(struct work_struct *work)
{
unsigned char c;
unsigned short i, j, k, b;
if (memcmp(cfag12864b_cache, cfag12864b_buffer, CFAG12864B_SIZE)) {
for (i = 0; i < CFAG12864B_CONTROLLERS; i++) {
cfag12864b_controller(i);
cfag12864b_nop();
for (j = 0; j < CFAG12864B_PAGES; j++) {
cfag12864b_page(j);
cfag12864b_nop();
cfag12864b_address(0);
cfag12864b_nop();
for (k = 0; k < CFAG12864B_ADDRESSES; k++) {
for (c = 0, b = 0; b < 8; b++)
if (cfag12864b_buffer
[i * CFAG12864B_ADDRESSES / 8
+ k / 8 + (j * 8 + b) *
CFAG12864B_WIDTH / 8]
& bit(k % 8))
c |= bit(b);
cfag12864b_writebyte(c);
}
}
}
memcpy(cfag12864b_cache, cfag12864b_buffer, CFAG12864B_SIZE);
}
if (cfag12864b_updating)
cfag12864b_queue();
}
/*
* cfag12864b Exported Symbols
*/
EXPORT_SYMBOL_GPL(cfag12864b_buffer);
EXPORT_SYMBOL_GPL(cfag12864b_enable);
EXPORT_SYMBOL_GPL(cfag12864b_disable);
/*
* Is the module inited?
*/
static unsigned char cfag12864b_inited;
unsigned char cfag12864b_isinited(void)
{
return cfag12864b_inited;
}
EXPORT_SYMBOL_GPL(cfag12864b_isinited);
/*
* Module Init & Exit
*/
static int __init cfag12864b_init(void)
{
int ret = -EINVAL;
/* ks0108_init() must be called first */
if (!ks0108_isinited()) {
printk(KERN_ERR CFAG12864B_NAME ": ERROR: "
"ks0108 is not initialized\n");
goto none;
}
BUILD_BUG_ON(PAGE_SIZE < CFAG12864B_SIZE);
cfag12864b_buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL);
if (cfag12864b_buffer == NULL) {
printk(KERN_ERR CFAG12864B_NAME ": ERROR: "
"can't get a free page\n");
ret = -ENOMEM;
goto none;
}
cfag12864b_cache = kmalloc(CFAG12864B_SIZE,
GFP_KERNEL);
if (cfag12864b_cache == NULL) {
printk(KERN_ERR CFAG12864B_NAME ": ERROR: "
"can't alloc cache buffer (%i bytes)\n",
CFAG12864B_SIZE);
ret = -ENOMEM;
goto bufferalloced;
}
cfag12864b_workqueue = create_singlethread_workqueue(CFAG12864B_NAME);
if (cfag12864b_workqueue == NULL)
goto cachealloced;
cfag12864b_clear();
cfag12864b_on();
cfag12864b_inited = 1;
return 0;
cachealloced:
kfree(cfag12864b_cache);
bufferalloced:
free_page((unsigned long) cfag12864b_buffer);
none:
return ret;
}
static void __exit cfag12864b_exit(void)
{
cfag12864b_disable();
cfag12864b_off();
destroy_workqueue(cfag12864b_workqueue);
kfree(cfag12864b_cache);
free_page((unsigned long) cfag12864b_buffer);
}
module_init(cfag12864b_init);
module_exit(cfag12864b_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Miguel Ojeda <ojeda@kernel.org>");
MODULE_DESCRIPTION("cfag12864b LCD driver");
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Filename: cfag12864bfb.c
* Version: 0.1.0
* Description: cfag12864b LCD framebuffer driver
* Depends: cfag12864b
*
* Author: Copyright (C) Miguel Ojeda <ojeda@kernel.org>
* Date: 2006-10-31
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/mm.h>
#include <linux/platform_device.h>
#include <linux/cfag12864b.h>
#define CFAG12864BFB_NAME "cfag12864bfb"
static const struct fb_fix_screeninfo cfag12864bfb_fix = {
.id = "cfag12864b",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_MONO10,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.line_length = CFAG12864B_WIDTH / 8,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo cfag12864bfb_var = {
.xres = CFAG12864B_WIDTH,
.yres = CFAG12864B_HEIGHT,
.xres_virtual = CFAG12864B_WIDTH,
.yres_virtual = CFAG12864B_HEIGHT,
.bits_per_pixel = 1,
.red = { 0, 1, 0 },
.green = { 0, 1, 0 },
.blue = { 0, 1, 0 },
.left_margin = 0,
.right_margin = 0,
.upper_margin = 0,
.lower_margin = 0,
.vmode = FB_VMODE_NONINTERLACED,
};
static int cfag12864bfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct page *pages = virt_to_page(cfag12864b_buffer);
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
return vm_map_pages_zero(vma, &pages, 1);
}
static const struct fb_ops cfag12864bfb_ops = {
.owner = THIS_MODULE,
__FB_DEFAULT_SYSMEM_OPS_RDWR,
__FB_DEFAULT_SYSMEM_OPS_DRAW,
.fb_mmap = cfag12864bfb_mmap,
};
static int cfag12864bfb_probe(struct platform_device *device)
{
int ret = -EINVAL;
struct fb_info *info = framebuffer_alloc(0, &device->dev);
if (!info)
goto none;
info->flags = FBINFO_VIRTFB;
info->screen_buffer = cfag12864b_buffer;
info->screen_size = CFAG12864B_SIZE;
info->fbops = &cfag12864bfb_ops;
info->fix = cfag12864bfb_fix;
info->var = cfag12864bfb_var;
info->pseudo_palette = NULL;
info->par = NULL;
if (register_framebuffer(info) < 0)
goto fballoced;
platform_set_drvdata(device, info);
fb_info(info, "%s frame buffer device\n", info->fix.id);
return 0;
fballoced:
framebuffer_release(info);
none:
return ret;
}
static void cfag12864bfb_remove(struct platform_device *device)
{
struct fb_info *info = platform_get_drvdata(device);
if (info) {
unregister_framebuffer(info);
framebuffer_release(info);
}
}
static struct platform_driver cfag12864bfb_driver = {
.probe = cfag12864bfb_probe,
.remove = cfag12864bfb_remove,
.driver = {
.name = CFAG12864BFB_NAME,
},
};
static struct platform_device *cfag12864bfb_device;
static int __init cfag12864bfb_init(void)
{
int ret = -EINVAL;
/* cfag12864b_init() must be called first */
if (!cfag12864b_isinited()) {
printk(KERN_ERR CFAG12864BFB_NAME ": ERROR: "
"cfag12864b is not initialized\n");
goto none;
}
if (cfag12864b_enable()) {
printk(KERN_ERR CFAG12864BFB_NAME ": ERROR: "
"can't enable cfag12864b refreshing (being used)\n");
return -ENODEV;
}
ret = platform_driver_register(&cfag12864bfb_driver);
if (!ret) {
cfag12864bfb_device =
platform_device_alloc(CFAG12864BFB_NAME, 0);
if (cfag12864bfb_device)
ret = platform_device_add(cfag12864bfb_device);
else
ret = -ENOMEM;
if (ret) {
platform_device_put(cfag12864bfb_device);
platform_driver_unregister(&cfag12864bfb_driver);
}
}
none:
return ret;
}
static void __exit cfag12864bfb_exit(void)
{
platform_device_unregister(cfag12864bfb_device);
platform_driver_unregister(&cfag12864bfb_driver);
cfag12864b_disable();
}
module_init(cfag12864bfb_init);
module_exit(cfag12864bfb_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Miguel Ojeda <ojeda@kernel.org>");
MODULE_DESCRIPTION("cfag12864b LCD framebuffer driver");
+684
View File
@@ -0,0 +1,684 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Character LCD driver for Linux
*
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
* Copyright (C) 2016-2017 Glider bvba
*/
#include <linux/atomic.h>
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#ifndef CONFIG_PANEL_BOOT_MESSAGE
#include <generated/utsrelease.h>
#endif
#include "charlcd.h"
/* Keep the backlight on this many seconds for each flash */
#define LCD_BL_TEMPO_PERIOD 4
#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
struct charlcd_priv {
struct charlcd lcd;
struct delayed_work bl_work;
struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
bool bl_tempo;
bool must_clear;
/* contains the LCD config state */
unsigned long flags;
/* Current escape sequence and it's length or -1 if outside */
struct {
char buf[LCD_ESCAPE_LEN + 1];
int len;
} esc_seq;
unsigned long long drvdata[];
};
#define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
/* Device single-open policy control */
static atomic_t charlcd_available = ATOMIC_INIT(1);
/* turn the backlight on or off */
void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
if (!lcd->ops->backlight)
return;
mutex_lock(&priv->bl_tempo_lock);
if (!priv->bl_tempo)
lcd->ops->backlight(lcd, on);
mutex_unlock(&priv->bl_tempo_lock);
}
EXPORT_SYMBOL_GPL(charlcd_backlight);
static void charlcd_bl_off(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct charlcd_priv *priv =
container_of(dwork, struct charlcd_priv, bl_work);
mutex_lock(&priv->bl_tempo_lock);
if (priv->bl_tempo) {
priv->bl_tempo = false;
if (!(priv->flags & LCD_FLAG_L))
priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
}
mutex_unlock(&priv->bl_tempo_lock);
}
/* turn the backlight on for a little while */
void charlcd_poke(struct charlcd *lcd)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
if (!lcd->ops->backlight)
return;
cancel_delayed_work_sync(&priv->bl_work);
mutex_lock(&priv->bl_tempo_lock);
if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
lcd->ops->backlight(lcd, CHARLCD_ON);
priv->bl_tempo = true;
schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
mutex_unlock(&priv->bl_tempo_lock);
}
EXPORT_SYMBOL_GPL(charlcd_poke);
static void charlcd_home(struct charlcd *lcd)
{
lcd->addr.x = 0;
lcd->addr.y = 0;
lcd->ops->home(lcd);
}
static void charlcd_print(struct charlcd *lcd, char c)
{
if (lcd->addr.x >= lcd->width)
return;
if (lcd->char_conv)
c = lcd->char_conv[(unsigned char)c];
if (!lcd->ops->print(lcd, c))
lcd->addr.x++;
/* prevents the cursor from wrapping onto the next line */
if (lcd->addr.x == lcd->width)
lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
}
static void charlcd_clear_display(struct charlcd *lcd)
{
lcd->ops->clear_display(lcd);
lcd->addr.x = 0;
lcd->addr.y = 0;
}
/*
* Parses a movement command of the form "(.*);", where the group can be
* any number of subcommands of the form "(x|y)[0-9]+".
*
* Returns whether the command is valid. The position arguments are
* only written if the parsing was successful.
*
* For instance:
* - ";" returns (<original x>, <original y>).
* - "x1;" returns (1, <original y>).
* - "y2x1;" returns (1, 2).
* - "x12y34x56;" returns (56, 34).
* - "" fails.
* - "x" fails.
* - "x;" fails.
* - "x1" fails.
* - "xy12;" fails.
* - "x12yy12;" fails.
* - "xx" fails.
*/
static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
{
unsigned long new_x = *x;
unsigned long new_y = *y;
char *p;
for (;;) {
if (!*s)
return false;
if (*s == ';')
break;
if (*s == 'x') {
new_x = simple_strtoul(s + 1, &p, 10);
if (p == s + 1)
return false;
s = p;
} else if (*s == 'y') {
new_y = simple_strtoul(s + 1, &p, 10);
if (p == s + 1)
return false;
s = p;
} else {
return false;
}
}
*x = new_x;
*y = new_y;
return true;
}
/*
* These are the file operation function for user access to /dev/lcd
* This function can also be called from inside the kernel, by
* setting file and ppos to NULL.
*
*/
static inline int handle_lcd_special_code(struct charlcd *lcd)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
/* LCD special codes */
int processed = 0;
char *esc = priv->esc_seq.buf + 2;
int oldflags = priv->flags;
/* check for display mode flags */
switch (*esc) {
case 'D': /* Display ON */
priv->flags |= LCD_FLAG_D;
if (priv->flags != oldflags)
lcd->ops->display(lcd, CHARLCD_ON);
processed = 1;
break;
case 'd': /* Display OFF */
priv->flags &= ~LCD_FLAG_D;
if (priv->flags != oldflags)
lcd->ops->display(lcd, CHARLCD_OFF);
processed = 1;
break;
case 'C': /* Cursor ON */
priv->flags |= LCD_FLAG_C;
if (priv->flags != oldflags)
lcd->ops->cursor(lcd, CHARLCD_ON);
processed = 1;
break;
case 'c': /* Cursor OFF */
priv->flags &= ~LCD_FLAG_C;
if (priv->flags != oldflags)
lcd->ops->cursor(lcd, CHARLCD_OFF);
processed = 1;
break;
case 'B': /* Blink ON */
priv->flags |= LCD_FLAG_B;
if (priv->flags != oldflags)
lcd->ops->blink(lcd, CHARLCD_ON);
processed = 1;
break;
case 'b': /* Blink OFF */
priv->flags &= ~LCD_FLAG_B;
if (priv->flags != oldflags)
lcd->ops->blink(lcd, CHARLCD_OFF);
processed = 1;
break;
case '+': /* Back light ON */
priv->flags |= LCD_FLAG_L;
if (priv->flags != oldflags)
charlcd_backlight(lcd, CHARLCD_ON);
processed = 1;
break;
case '-': /* Back light OFF */
priv->flags &= ~LCD_FLAG_L;
if (priv->flags != oldflags)
charlcd_backlight(lcd, CHARLCD_OFF);
processed = 1;
break;
case '*': /* Flash back light */
charlcd_poke(lcd);
processed = 1;
break;
case 'f': /* Small Font */
priv->flags &= ~LCD_FLAG_F;
if (priv->flags != oldflags)
lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
processed = 1;
break;
case 'F': /* Large Font */
priv->flags |= LCD_FLAG_F;
if (priv->flags != oldflags)
lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
processed = 1;
break;
case 'n': /* One Line */
priv->flags &= ~LCD_FLAG_N;
if (priv->flags != oldflags)
lcd->ops->lines(lcd, CHARLCD_LINES_1);
processed = 1;
break;
case 'N': /* Two Lines */
priv->flags |= LCD_FLAG_N;
if (priv->flags != oldflags)
lcd->ops->lines(lcd, CHARLCD_LINES_2);
processed = 1;
break;
case 'l': /* Shift Cursor Left */
if (lcd->addr.x > 0) {
if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
lcd->addr.x--;
}
processed = 1;
break;
case 'r': /* shift cursor right */
if (lcd->addr.x < lcd->width) {
if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
lcd->addr.x++;
}
processed = 1;
break;
case 'L': /* shift display left */
lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
processed = 1;
break;
case 'R': /* shift display right */
lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
processed = 1;
break;
case 'k': { /* kill end of line */
int x, xs, ys;
xs = lcd->addr.x;
ys = lcd->addr.y;
for (x = lcd->addr.x; x < lcd->width; x++)
lcd->ops->print(lcd, ' ');
/* restore cursor position */
lcd->addr.x = xs;
lcd->addr.y = ys;
lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
processed = 1;
break;
}
case 'I': /* reinitialize display */
lcd->ops->init_display(lcd);
priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
LCD_FLAG_C | LCD_FLAG_B;
processed = 1;
break;
case 'G':
if (lcd->ops->redefine_char)
processed = lcd->ops->redefine_char(lcd, esc);
else
processed = 1;
break;
case 'x': /* gotoxy : LxXXX[yYYY]; */
case 'y': /* gotoxy : LyYYY[xXXX]; */
if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
break;
/* If the command is valid, move to the new address */
if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
/* Regardless of its validity, mark as processed */
processed = 1;
break;
}
return processed;
}
static void charlcd_write_char(struct charlcd *lcd, char c)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
/* first, we'll test if we're in escape mode */
if ((c != '\n') && priv->esc_seq.len >= 0) {
/* yes, let's add this char to the buffer */
priv->esc_seq.buf[priv->esc_seq.len++] = c;
priv->esc_seq.buf[priv->esc_seq.len] = '\0';
} else {
/* aborts any previous escape sequence */
priv->esc_seq.len = -1;
switch (c) {
case LCD_ESCAPE_CHAR:
/* start of an escape sequence */
priv->esc_seq.len = 0;
priv->esc_seq.buf[priv->esc_seq.len] = '\0';
break;
case '\b':
/* go back one char and clear it */
if (lcd->addr.x > 0) {
/* back one char */
if (!lcd->ops->shift_cursor(lcd,
CHARLCD_SHIFT_LEFT))
lcd->addr.x--;
}
/* replace with a space */
charlcd_print(lcd, ' ');
/* back one char again */
if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
lcd->addr.x--;
break;
case '\f':
/* quickly clear the display */
charlcd_clear_display(lcd);
break;
case '\n':
/*
* flush the remainder of the current line and
* go to the beginning of the next line
*/
for (; lcd->addr.x < lcd->width; lcd->addr.x++)
lcd->ops->print(lcd, ' ');
lcd->addr.x = 0;
lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
break;
case '\r':
/* go to the beginning of the same line */
lcd->addr.x = 0;
lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
break;
case '\t':
/* print a space instead of the tab */
charlcd_print(lcd, ' ');
break;
default:
/* simply print this char */
charlcd_print(lcd, c);
break;
}
}
/*
* now we'll see if we're in an escape mode and if the current
* escape sequence can be understood.
*/
if (priv->esc_seq.len >= 2) {
int processed = 0;
if (!strcmp(priv->esc_seq.buf, "[2J")) {
/* clear the display */
charlcd_clear_display(lcd);
processed = 1;
} else if (!strcmp(priv->esc_seq.buf, "[H")) {
/* cursor to home */
charlcd_home(lcd);
processed = 1;
}
/* codes starting with ^[[L */
else if ((priv->esc_seq.len >= 3) &&
(priv->esc_seq.buf[0] == '[') &&
(priv->esc_seq.buf[1] == 'L')) {
processed = handle_lcd_special_code(lcd);
}
/* LCD special escape codes */
/*
* flush the escape sequence if it's been processed
* or if it is getting too long.
*/
if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
priv->esc_seq.len = -1;
} /* escape codes */
}
static struct charlcd *the_charlcd;
static ssize_t charlcd_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
const char __user *tmp = buf;
char c;
for (; count-- > 0; (*ppos)++, tmp++) {
if (((count + 1) & 0x1f) == 0) {
/*
* charlcd_write() is invoked as a VFS->write() callback
* and as such it is always invoked from preemptible
* context and may sleep.
*/
cond_resched();
}
if (get_user(c, tmp))
return -EFAULT;
charlcd_write_char(the_charlcd, c);
}
return tmp - buf;
}
static int charlcd_open(struct inode *inode, struct file *file)
{
struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
int ret;
ret = -EBUSY;
if (!atomic_dec_and_test(&charlcd_available))
goto fail; /* open only once at a time */
ret = -EPERM;
if (file->f_mode & FMODE_READ) /* device is write-only */
goto fail;
if (priv->must_clear) {
priv->lcd.ops->clear_display(&priv->lcd);
priv->must_clear = false;
priv->lcd.addr.x = 0;
priv->lcd.addr.y = 0;
}
return nonseekable_open(inode, file);
fail:
atomic_inc(&charlcd_available);
return ret;
}
static int charlcd_release(struct inode *inode, struct file *file)
{
atomic_inc(&charlcd_available);
return 0;
}
static const struct file_operations charlcd_fops = {
.write = charlcd_write,
.open = charlcd_open,
.release = charlcd_release,
};
static struct miscdevice charlcd_dev = {
.minor = LCD_MINOR,
.name = "lcd",
.fops = &charlcd_fops,
};
static void charlcd_puts(struct charlcd *lcd, const char *s)
{
const char *tmp = s;
int count = strlen(s);
for (; count-- > 0; tmp++) {
if (((count + 1) & 0x1f) == 0)
cond_resched();
charlcd_write_char(lcd, *tmp);
}
}
#ifdef CONFIG_PANEL_BOOT_MESSAGE
#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
#else
#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
#endif
#ifdef CONFIG_CHARLCD_BL_ON
#define LCD_INIT_BL "\x1b[L+"
#elif defined(CONFIG_CHARLCD_BL_FLASH)
#define LCD_INIT_BL "\x1b[L*"
#else
#define LCD_INIT_BL "\x1b[L-"
#endif
/* initialize the LCD driver */
static int charlcd_init(struct charlcd *lcd)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
int ret;
priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
LCD_FLAG_C | LCD_FLAG_B;
if (lcd->ops->backlight) {
mutex_init(&priv->bl_tempo_lock);
INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
}
/*
* before this line, we must NOT send anything to the display.
* Since charlcd_init_display() needs to write data, we have to
* enable mark the LCD initialized just before.
*/
if (WARN_ON(!lcd->ops->init_display))
return -EINVAL;
ret = lcd->ops->init_display(lcd);
if (ret)
return ret;
/* display a short message */
charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
/* clear the display on the next device opening */
priv->must_clear = true;
charlcd_home(lcd);
return 0;
}
struct charlcd *charlcd_alloc(unsigned int drvdata_size)
{
struct charlcd_priv *priv;
struct charlcd *lcd;
priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL);
if (!priv)
return NULL;
priv->esc_seq.len = -1;
lcd = &priv->lcd;
lcd->drvdata = priv->drvdata;
return lcd;
}
EXPORT_SYMBOL_GPL(charlcd_alloc);
void charlcd_free(struct charlcd *lcd)
{
kfree(charlcd_to_priv(lcd));
}
EXPORT_SYMBOL_GPL(charlcd_free);
static int panel_notify_sys(struct notifier_block *this, unsigned long code,
void *unused)
{
struct charlcd *lcd = the_charlcd;
switch (code) {
case SYS_DOWN:
charlcd_puts(lcd,
"\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
break;
case SYS_HALT:
charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
break;
case SYS_POWER_OFF:
charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
break;
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block panel_notifier = {
.notifier_call = panel_notify_sys,
};
int charlcd_register(struct charlcd *lcd)
{
int ret;
ret = charlcd_init(lcd);
if (ret)
return ret;
ret = misc_register(&charlcd_dev);
if (ret)
return ret;
the_charlcd = lcd;
register_reboot_notifier(&panel_notifier);
return 0;
}
EXPORT_SYMBOL_GPL(charlcd_register);
int charlcd_unregister(struct charlcd *lcd)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
unregister_reboot_notifier(&panel_notifier);
charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
misc_deregister(&charlcd_dev);
the_charlcd = NULL;
if (lcd->ops->backlight) {
cancel_delayed_work_sync(&priv->bl_work);
priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
}
return 0;
}
EXPORT_SYMBOL_GPL(charlcd_unregister);
MODULE_DESCRIPTION("Character LCD core support");
MODULE_LICENSE("GPL");
+107
View File
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Character LCD driver for Linux
*
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
* Copyright (C) 2016-2017 Glider bvba
*/
#ifndef _CHARLCD_H
#define _CHARLCD_H
#define LCD_FLAG_B 0x0004 /* Blink on */
#define LCD_FLAG_C 0x0008 /* Cursor on */
#define LCD_FLAG_D 0x0010 /* Display on */
#define LCD_FLAG_F 0x0020 /* Large font mode */
#define LCD_FLAG_N 0x0040 /* 2-rows mode */
#define LCD_FLAG_L 0x0080 /* Backlight enabled */
enum charlcd_onoff {
CHARLCD_OFF = 0,
CHARLCD_ON,
};
enum charlcd_shift_dir {
CHARLCD_SHIFT_LEFT,
CHARLCD_SHIFT_RIGHT,
};
enum charlcd_fontsize {
CHARLCD_FONTSIZE_SMALL,
CHARLCD_FONTSIZE_LARGE,
};
enum charlcd_lines {
CHARLCD_LINES_1,
CHARLCD_LINES_2,
};
struct charlcd_ops;
struct charlcd {
const struct charlcd_ops *ops;
const unsigned char *char_conv; /* Optional */
int height;
int width;
/* Contains the LCD X and Y offset */
struct {
unsigned long x;
unsigned long y;
} addr;
void *drvdata; /* Set by charlcd_alloc() */
};
/**
* struct charlcd_ops - Functions used by charlcd. Drivers have to implement
* these.
* @backlight: Turn backlight on or off. Optional.
* @print: Print one character to the display at current cursor position.
* The buffered cursor position is advanced by charlcd. The cursor should not
* wrap to the next line at the end of a line.
* @gotoxy: Set cursor to x, y. The x and y values to set the cursor to are
* previously set in addr.x and addr.y by charlcd.
* @home: Set cursor to 0, 0. The values in addr.x and addr.y are set to 0, 0 by
* charlcd prior to calling this function.
* @clear_display: Clear the whole display and set the cursor to 0, 0. The
* values in addr.x and addr.y are set to 0, 0 by charlcd after to calling this
* function.
* @init_display: Initialize the display.
* @shift_cursor: Shift cursor left or right one position.
* @shift_display: Shift whole display content left or right.
* @display: Turn display on or off.
* @cursor: Turn cursor on or off.
* @blink: Turn cursor blink on or off.
* @lines: One or two lines.
* @redefine_char: Redefine the actual pixel matrix of character.
*/
struct charlcd_ops {
void (*backlight)(struct charlcd *lcd, enum charlcd_onoff on);
int (*print)(struct charlcd *lcd, int c);
int (*gotoxy)(struct charlcd *lcd, unsigned int x, unsigned int y);
int (*home)(struct charlcd *lcd);
int (*clear_display)(struct charlcd *lcd);
int (*init_display)(struct charlcd *lcd);
int (*shift_cursor)(struct charlcd *lcd, enum charlcd_shift_dir dir);
int (*shift_display)(struct charlcd *lcd, enum charlcd_shift_dir dir);
int (*display)(struct charlcd *lcd, enum charlcd_onoff on);
int (*cursor)(struct charlcd *lcd, enum charlcd_onoff on);
int (*blink)(struct charlcd *lcd, enum charlcd_onoff on);
int (*fontsize)(struct charlcd *lcd, enum charlcd_fontsize size);
int (*lines)(struct charlcd *lcd, enum charlcd_lines lines);
int (*redefine_char)(struct charlcd *lcd, char *esc);
};
void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on);
struct charlcd *charlcd_alloc(unsigned int drvdata_size);
void charlcd_free(struct charlcd *lcd);
int charlcd_register(struct charlcd *lcd);
int charlcd_unregister(struct charlcd *lcd);
void charlcd_poke(struct charlcd *lcd);
#endif /* CHARLCD_H */
+344
View File
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HD44780 Character LCD driver for Linux
*
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
* Copyright (C) 2016-2017 Glider bvba
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include "charlcd.h"
#include "hd44780_common.h"
enum hd44780_pin {
/* Order does matter due to writing to GPIO array subsets! */
PIN_DATA0, /* Optional */
PIN_DATA1, /* Optional */
PIN_DATA2, /* Optional */
PIN_DATA3, /* Optional */
PIN_DATA4,
PIN_DATA5,
PIN_DATA6,
PIN_DATA7,
PIN_CTRL_RS,
PIN_CTRL_RW, /* Optional */
PIN_CTRL_E,
PIN_CTRL_BL, /* Optional */
PIN_NUM
};
struct hd44780 {
struct gpio_desc *pins[PIN_NUM];
};
static void hd44780_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
struct hd44780_common *hdc = lcd->drvdata;
struct hd44780 *hd = hdc->hd44780;
if (hd->pins[PIN_CTRL_BL])
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
}
static void hd44780_strobe_gpio(struct hd44780 *hd)
{
/* Maintain the data during 20 us before the strobe */
udelay(20);
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1);
/* Maintain the strobe during 40 us */
udelay(40);
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0);
}
/* write to an LCD panel register in 8 bit GPIO mode */
static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs)
{
DECLARE_BITMAP(values, 10); /* for DATA[0-7], RS, RW */
unsigned int n;
values[0] = val;
__assign_bit(8, values, rs);
n = hd->pins[PIN_CTRL_RW] ? 10 : 9;
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], NULL, values);
hd44780_strobe_gpio(hd);
}
/* write to an LCD panel register in 4 bit GPIO mode */
static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
{
DECLARE_BITMAP(values, 6); /* for DATA[4-7], RS, RW */
unsigned int n;
/* High nibble + RS, RW */
values[0] = val >> 4;
__assign_bit(4, values, rs);
n = hd->pins[PIN_CTRL_RW] ? 6 : 5;
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], NULL, values);
hd44780_strobe_gpio(hd);
/* Low nibble */
values[0] &= ~0x0fUL;
values[0] |= val & 0x0f;
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], NULL, values);
hd44780_strobe_gpio(hd);
}
/* Send a command to the LCD panel in 8 bit GPIO mode */
static void hd44780_write_cmd_gpio8(struct hd44780_common *hdc, int cmd)
{
struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio8(hd, cmd, 0);
/* The shortest command takes at least 120 us */
udelay(120);
}
/* Send data to the LCD panel in 8 bit GPIO mode */
static void hd44780_write_data_gpio8(struct hd44780_common *hdc, int data)
{
struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio8(hd, data, 1);
/* The shortest data takes at least 45 us */
udelay(45);
}
static const struct charlcd_ops hd44780_ops_gpio8 = {
.backlight = hd44780_backlight,
.print = hd44780_common_print,
.gotoxy = hd44780_common_gotoxy,
.home = hd44780_common_home,
.clear_display = hd44780_common_clear_display,
.init_display = hd44780_common_init_display,
.shift_cursor = hd44780_common_shift_cursor,
.shift_display = hd44780_common_shift_display,
.display = hd44780_common_display,
.cursor = hd44780_common_cursor,
.blink = hd44780_common_blink,
.fontsize = hd44780_common_fontsize,
.lines = hd44780_common_lines,
.redefine_char = hd44780_common_redefine_char,
};
/* Send a command to the LCD panel in 4 bit GPIO mode */
static void hd44780_write_cmd_gpio4(struct hd44780_common *hdc, int cmd)
{
struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio4(hd, cmd, 0);
/* The shortest command takes at least 120 us */
udelay(120);
}
/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */
static void hd44780_write_cmd_raw_gpio4(struct hd44780_common *hdc, int cmd)
{
DECLARE_BITMAP(values, 6); /* for DATA[4-7], RS, RW */
struct hd44780 *hd = hdc->hd44780;
unsigned int n;
/* Command nibble + RS, RW */
values[0] = cmd & 0x0f;
n = hd->pins[PIN_CTRL_RW] ? 6 : 5;
/* Present the data to the port */
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], NULL, values);
hd44780_strobe_gpio(hd);
}
/* Send data to the LCD panel in 4 bit GPIO mode */
static void hd44780_write_data_gpio4(struct hd44780_common *hdc, int data)
{
struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio4(hd, data, 1);
/* The shortest data takes at least 45 us */
udelay(45);
}
static const struct charlcd_ops hd44780_ops_gpio4 = {
.backlight = hd44780_backlight,
.print = hd44780_common_print,
.gotoxy = hd44780_common_gotoxy,
.home = hd44780_common_home,
.clear_display = hd44780_common_clear_display,
.init_display = hd44780_common_init_display,
.shift_cursor = hd44780_common_shift_cursor,
.shift_display = hd44780_common_shift_display,
.display = hd44780_common_display,
.cursor = hd44780_common_cursor,
.blink = hd44780_common_blink,
.fontsize = hd44780_common_fontsize,
.lines = hd44780_common_lines,
.redefine_char = hd44780_common_redefine_char,
};
static int hd44780_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
unsigned int i, base;
struct charlcd *lcd;
struct hd44780_common *hdc;
struct hd44780 *hd;
int ifwidth, ret = -ENOMEM;
/* Required pins */
ifwidth = gpiod_count(dev, "data");
if (ifwidth < 0)
return ifwidth;
switch (ifwidth) {
case 4:
base = PIN_DATA4;
break;
case 8:
base = PIN_DATA0;
break;
default:
return -EINVAL;
}
lcd = hd44780_common_alloc();
if (!lcd)
return -ENOMEM;
hd = kzalloc_obj(*hd);
if (!hd)
goto fail2;
hdc = lcd->drvdata;
hdc->hd44780 = hd;
for (i = 0; i < ifwidth; i++) {
hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i,
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[base + i])) {
ret = PTR_ERR(hd->pins[base + i]);
goto fail3;
}
}
hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_E])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_E]);
goto fail3;
}
hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH);
if (IS_ERR(hd->pins[PIN_CTRL_RS])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RS]);
goto fail3;
}
/* Optional pins */
hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw",
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
goto fail3;
}
hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight",
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_BL])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_BL]);
goto fail3;
}
/* Required properties */
ret = device_property_read_u32(dev, "display-height-chars",
&lcd->height);
if (ret)
goto fail3;
ret = device_property_read_u32(dev, "display-width-chars", &lcd->width);
if (ret)
goto fail3;
/*
* On displays with more than two rows, the internal buffer width is
* usually equal to the display width
*/
if (lcd->height > 2)
hdc->bwidth = lcd->width;
/* Optional properties */
device_property_read_u32(dev, "internal-buffer-width", &hdc->bwidth);
hdc->ifwidth = ifwidth;
if (ifwidth == 8) {
lcd->ops = &hd44780_ops_gpio8;
hdc->write_data = hd44780_write_data_gpio8;
hdc->write_cmd = hd44780_write_cmd_gpio8;
} else {
lcd->ops = &hd44780_ops_gpio4;
hdc->write_data = hd44780_write_data_gpio4;
hdc->write_cmd = hd44780_write_cmd_gpio4;
hdc->write_cmd_raw4 = hd44780_write_cmd_raw_gpio4;
}
ret = charlcd_register(lcd);
if (ret)
goto fail3;
platform_set_drvdata(pdev, lcd);
return 0;
fail3:
kfree(hd);
fail2:
hd44780_common_free(lcd);
return ret;
}
static void hd44780_remove(struct platform_device *pdev)
{
struct charlcd *lcd = platform_get_drvdata(pdev);
struct hd44780_common *hdc = lcd->drvdata;
charlcd_unregister(lcd);
kfree(hdc->hd44780);
hd44780_common_free(lcd);
}
static const struct of_device_id hd44780_of_match[] = {
{ .compatible = "hit,hd44780" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, hd44780_of_match);
static struct platform_driver hd44780_driver = {
.probe = hd44780_probe,
.remove = hd44780_remove,
.driver = {
.name = "hd44780",
.of_match_table = hd44780_of_match,
},
};
module_platform_driver(hd44780_driver);
MODULE_DESCRIPTION("HD44780 Character LCD driver");
MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/hex.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "charlcd.h"
#include "hd44780_common.h"
/* LCD commands */
#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
/* sleeps that many milliseconds with a reschedule */
static void long_sleep(int ms)
{
schedule_timeout_interruptible(msecs_to_jiffies(ms));
}
int hd44780_common_print(struct charlcd *lcd, int c)
{
struct hd44780_common *hdc = lcd->drvdata;
if (lcd->addr.x < hdc->bwidth) {
hdc->write_data(hdc, c);
return 0;
}
return 1;
}
EXPORT_SYMBOL_GPL(hd44780_common_print);
int hd44780_common_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
{
struct hd44780_common *hdc = lcd->drvdata;
unsigned int addr;
/*
* we force the cursor to stay at the end of the
* line if it wants to go farther
*/
addr = x < hdc->bwidth ? x & (hdc->hwidth - 1) : hdc->bwidth - 1;
if (y & 1)
addr += hdc->hwidth;
if (y & 2)
addr += hdc->bwidth;
hdc->write_cmd(hdc, LCD_CMD_SET_DDRAM_ADDR | addr);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_gotoxy);
int hd44780_common_home(struct charlcd *lcd)
{
return hd44780_common_gotoxy(lcd, 0, 0);
}
EXPORT_SYMBOL_GPL(hd44780_common_home);
/* clears the display and resets X/Y */
int hd44780_common_clear_display(struct charlcd *lcd)
{
struct hd44780_common *hdc = lcd->drvdata;
hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CLEAR);
/* datasheet says to wait 1,64 milliseconds */
long_sleep(2);
/*
* The Hitachi HD44780 controller (and compatible ones) reset the DDRAM
* address when executing the DISPLAY_CLEAR command, thus the
* following call is not required. However, other controllers do not
* (e.g. NewHaven NHD-0220DZW-AG5), thus move the cursor to home
* unconditionally to support both.
*/
return hd44780_common_home(lcd);
}
EXPORT_SYMBOL_GPL(hd44780_common_clear_display);
int hd44780_common_init_display(struct charlcd *lcd)
{
struct hd44780_common *hdc = lcd->drvdata;
void (*write_cmd_raw)(struct hd44780_common *hdc, int cmd);
u8 init;
if (hdc->ifwidth != 4 && hdc->ifwidth != 8)
return -EINVAL;
hdc->hd44780_common_flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) |
LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B;
long_sleep(20); /* wait 20 ms after power-up for the paranoid */
/*
* 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
* the LCD is in 8-bit mode afterwards
*/
init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
if (hdc->ifwidth == 4) {
init >>= 4;
write_cmd_raw = hdc->write_cmd_raw4;
} else {
write_cmd_raw = hdc->write_cmd;
}
write_cmd_raw(hdc, init);
long_sleep(10);
write_cmd_raw(hdc, init);
long_sleep(10);
write_cmd_raw(hdc, init);
long_sleep(10);
if (hdc->ifwidth == 4) {
/* Switch to 4-bit mode, 1 line, small fonts */
hdc->write_cmd_raw4(hdc, LCD_CMD_FUNCTION_SET >> 4);
long_sleep(10);
}
/* set font height and lines number */
hdc->write_cmd(hdc,
LCD_CMD_FUNCTION_SET |
((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_F) ?
LCD_CMD_FONT_5X10_DOTS : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_N) ?
LCD_CMD_TWO_LINES : 0));
long_sleep(10);
/* display off, cursor off, blink off */
hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CTRL);
long_sleep(10);
hdc->write_cmd(hdc,
LCD_CMD_DISPLAY_CTRL | /* set display mode */
((hdc->hd44780_common_flags & LCD_FLAG_D) ?
LCD_CMD_DISPLAY_ON : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_C) ?
LCD_CMD_CURSOR_ON : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_B) ?
LCD_CMD_BLINK_ON : 0));
charlcd_backlight(lcd,
(hdc->hd44780_common_flags & LCD_FLAG_L) ? 1 : 0);
long_sleep(10);
/* entry mode set : increment, cursor shifting */
hdc->write_cmd(hdc, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
hd44780_common_clear_display(lcd);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_init_display);
int hd44780_common_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
{
struct hd44780_common *hdc = lcd->drvdata;
if (dir == CHARLCD_SHIFT_LEFT) {
/* back one char if not at end of line */
if (lcd->addr.x < hdc->bwidth)
hdc->write_cmd(hdc, LCD_CMD_SHIFT);
} else if (dir == CHARLCD_SHIFT_RIGHT) {
/* allow the cursor to pass the end of the line */
if (lcd->addr.x < (hdc->bwidth - 1))
hdc->write_cmd(hdc,
LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
}
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_shift_cursor);
int hd44780_common_shift_display(struct charlcd *lcd,
enum charlcd_shift_dir dir)
{
struct hd44780_common *hdc = lcd->drvdata;
if (dir == CHARLCD_SHIFT_LEFT)
hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
else if (dir == CHARLCD_SHIFT_RIGHT)
hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
LCD_CMD_SHIFT_RIGHT);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_shift_display);
static void hd44780_common_set_mode(struct hd44780_common *hdc)
{
hdc->write_cmd(hdc,
LCD_CMD_DISPLAY_CTRL |
((hdc->hd44780_common_flags & LCD_FLAG_D) ?
LCD_CMD_DISPLAY_ON : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_C) ?
LCD_CMD_CURSOR_ON : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_B) ?
LCD_CMD_BLINK_ON : 0));
}
int hd44780_common_display(struct charlcd *lcd, enum charlcd_onoff on)
{
struct hd44780_common *hdc = lcd->drvdata;
if (on == CHARLCD_ON)
hdc->hd44780_common_flags |= LCD_FLAG_D;
else
hdc->hd44780_common_flags &= ~LCD_FLAG_D;
hd44780_common_set_mode(hdc);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_display);
int hd44780_common_cursor(struct charlcd *lcd, enum charlcd_onoff on)
{
struct hd44780_common *hdc = lcd->drvdata;
if (on == CHARLCD_ON)
hdc->hd44780_common_flags |= LCD_FLAG_C;
else
hdc->hd44780_common_flags &= ~LCD_FLAG_C;
hd44780_common_set_mode(hdc);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_cursor);
int hd44780_common_blink(struct charlcd *lcd, enum charlcd_onoff on)
{
struct hd44780_common *hdc = lcd->drvdata;
if (on == CHARLCD_ON)
hdc->hd44780_common_flags |= LCD_FLAG_B;
else
hdc->hd44780_common_flags &= ~LCD_FLAG_B;
hd44780_common_set_mode(hdc);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_blink);
static void hd44780_common_set_function(struct hd44780_common *hdc)
{
hdc->write_cmd(hdc,
LCD_CMD_FUNCTION_SET |
((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_F) ?
LCD_CMD_FONT_5X10_DOTS : 0) |
((hdc->hd44780_common_flags & LCD_FLAG_N) ?
LCD_CMD_TWO_LINES : 0));
}
int hd44780_common_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
{
struct hd44780_common *hdc = lcd->drvdata;
if (size == CHARLCD_FONTSIZE_LARGE)
hdc->hd44780_common_flags |= LCD_FLAG_F;
else
hdc->hd44780_common_flags &= ~LCD_FLAG_F;
hd44780_common_set_function(hdc);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_fontsize);
int hd44780_common_lines(struct charlcd *lcd, enum charlcd_lines lines)
{
struct hd44780_common *hdc = lcd->drvdata;
if (lines == CHARLCD_LINES_2)
hdc->hd44780_common_flags |= LCD_FLAG_N;
else
hdc->hd44780_common_flags &= ~LCD_FLAG_N;
hd44780_common_set_function(hdc);
return 0;
}
EXPORT_SYMBOL_GPL(hd44780_common_lines);
int hd44780_common_redefine_char(struct charlcd *lcd, char *esc)
{
/* Generator : LGcxxxxx...xx; must have <c> between '0'
* and '7', representing the numerical ASCII code of the
* redefined character, and <xx...xx> a sequence of 16
* hex digits representing 8 bytes for each character.
* Most LCDs will only use 5 lower bits of the 7 first
* bytes.
*/
struct hd44780_common *hdc = lcd->drvdata;
unsigned char cgbytes[8];
unsigned char cgaddr;
int cgoffset;
int shift;
char value;
int addr;
if (!strchr(esc, ';'))
return 0;
esc++;
cgaddr = *(esc++) - '0';
if (cgaddr > 7)
return 1;
cgoffset = 0;
shift = 0;
value = 0;
while (*esc && cgoffset < 8) {
int half;
shift ^= 4;
half = hex_to_bin(*esc++);
if (half < 0)
continue;
value |= half << shift;
if (shift == 0) {
cgbytes[cgoffset++] = value;
value = 0;
}
}
hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
for (addr = 0; addr < cgoffset; addr++)
hdc->write_data(hdc, cgbytes[addr]);
/* ensures that we stop writing to CGRAM */
lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
return 1;
}
EXPORT_SYMBOL_GPL(hd44780_common_redefine_char);
struct charlcd *hd44780_common_alloc(void)
{
struct hd44780_common *hdc;
struct charlcd *lcd;
lcd = charlcd_alloc(sizeof(*hdc));
if (!lcd)
return NULL;
hdc = lcd->drvdata;
hdc->ifwidth = 8;
hdc->bwidth = DEFAULT_LCD_BWIDTH;
hdc->hwidth = DEFAULT_LCD_HWIDTH;
return lcd;
}
EXPORT_SYMBOL_GPL(hd44780_common_alloc);
void hd44780_common_free(struct charlcd *lcd)
{
charlcd_free(lcd);
}
EXPORT_SYMBOL_GPL(hd44780_common_free);
MODULE_DESCRIPTION("Common functions for HD44780 (and compatibles) LCD displays");
MODULE_LICENSE("GPL");
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#define DEFAULT_LCD_BWIDTH 40
#define DEFAULT_LCD_HWIDTH 64
struct hd44780_common {
int ifwidth; /* 4-bit or 8-bit (default) */
int bwidth; /* Default set by hd44780_alloc() */
int hwidth; /* Default set by hd44780_alloc() */
unsigned long hd44780_common_flags;
void (*write_data)(struct hd44780_common *hdc, int data);
void (*write_cmd)(struct hd44780_common *hdc, int cmd);
/* write_cmd_raw4 is for 4-bit connected displays only */
void (*write_cmd_raw4)(struct hd44780_common *hdc, int cmd);
void *hd44780;
};
int hd44780_common_print(struct charlcd *lcd, int c);
int hd44780_common_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y);
int hd44780_common_home(struct charlcd *lcd);
int hd44780_common_clear_display(struct charlcd *lcd);
int hd44780_common_init_display(struct charlcd *lcd);
int hd44780_common_shift_cursor(struct charlcd *lcd,
enum charlcd_shift_dir dir);
int hd44780_common_shift_display(struct charlcd *lcd,
enum charlcd_shift_dir dir);
int hd44780_common_display(struct charlcd *lcd, enum charlcd_onoff on);
int hd44780_common_cursor(struct charlcd *lcd, enum charlcd_onoff on);
int hd44780_common_blink(struct charlcd *lcd, enum charlcd_onoff on);
int hd44780_common_fontsize(struct charlcd *lcd, enum charlcd_fontsize size);
int hd44780_common_lines(struct charlcd *lcd, enum charlcd_lines lines);
int hd44780_common_redefine_char(struct charlcd *lcd, char *esc);
struct charlcd *hd44780_common_alloc(void);
void hd44780_common_free(struct charlcd *lcd);
+784
View File
@@ -0,0 +1,784 @@
// SPDX-License-Identifier: GPL-2.0
/*
* HT16K33 driver
*
* Author: Robin van der Gracht <robin@protonic.nl>
*
* Copyright: (C) 2016 Protonic Holland.
* Copyright (C) 2021 Glider bv
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/property.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/container_of.h>
#include <linux/input.h>
#include <linux/input/matrix_keypad.h>
#include <linux/leds.h>
#include <linux/workqueue.h>
#include <linux/mm.h>
#include <linux/map_to_7segment.h>
#include <linux/map_to_14segment.h>
#include <linux/unaligned.h>
#include "line-display.h"
/* Registers */
#define REG_SYSTEM_SETUP 0x20
#define REG_SYSTEM_SETUP_OSC_ON BIT(0)
#define REG_DISPLAY_SETUP 0x80
#define REG_DISPLAY_SETUP_ON BIT(0)
#define REG_DISPLAY_SETUP_BLINK_OFF (0 << 1)
#define REG_DISPLAY_SETUP_BLINK_2HZ (1 << 1)
#define REG_DISPLAY_SETUP_BLINK_1HZ (2 << 1)
#define REG_DISPLAY_SETUP_BLINK_0HZ5 (3 << 1)
#define REG_ROWINT_SET 0xA0
#define REG_ROWINT_SET_INT_EN BIT(0)
#define REG_ROWINT_SET_INT_ACT_HIGH BIT(1)
#define REG_BRIGHTNESS 0xE0
/* Defines */
#define DRIVER_NAME "ht16k33"
#define MIN_BRIGHTNESS 0x1
#define MAX_BRIGHTNESS 0x10
#define HT16K33_MATRIX_LED_MAX_COLS 8
#define HT16K33_MATRIX_LED_MAX_ROWS 16
#define HT16K33_MATRIX_KEYPAD_MAX_COLS 3
#define HT16K33_MATRIX_KEYPAD_MAX_ROWS 12
#define BYTES_PER_ROW (HT16K33_MATRIX_LED_MAX_ROWS / 8)
#define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW)
enum display_type {
DISP_MATRIX = 0,
DISP_QUAD_7SEG,
DISP_QUAD_14SEG,
};
struct ht16k33_keypad {
struct i2c_client *client;
struct input_dev *dev;
uint32_t cols;
uint32_t rows;
uint32_t row_shift;
uint32_t debounce_ms;
uint16_t last_key_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
wait_queue_head_t wait;
bool stopped;
};
struct ht16k33_fbdev {
struct fb_info *info;
uint32_t refresh_rate;
uint8_t *buffer;
uint8_t *cache;
};
struct ht16k33_priv {
struct i2c_client *client;
struct delayed_work work;
struct led_classdev led;
struct ht16k33_keypad keypad;
union {
struct ht16k33_fbdev fbdev;
struct linedisp linedisp;
};
enum display_type type;
uint8_t blink;
};
#define ht16k33_work_to_priv(p) \
container_of(p, struct ht16k33_priv, work.work)
#define ht16k33_led_to_priv(p) \
container_of(p, struct ht16k33_priv, led)
#define ht16k33_linedisp_to_priv(p) \
container_of(p, struct ht16k33_priv, linedisp)
static const struct fb_fix_screeninfo ht16k33_fb_fix = {
.id = DRIVER_NAME,
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_MONO10,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.line_length = HT16K33_MATRIX_LED_MAX_ROWS,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo ht16k33_fb_var = {
.xres = HT16K33_MATRIX_LED_MAX_ROWS,
.yres = HT16K33_MATRIX_LED_MAX_COLS,
.xres_virtual = HT16K33_MATRIX_LED_MAX_ROWS,
.yres_virtual = HT16K33_MATRIX_LED_MAX_COLS,
.bits_per_pixel = 1,
.red = { 0, 1, 0 },
.green = { 0, 1, 0 },
.blue = { 0, 1, 0 },
.left_margin = 0,
.right_margin = 0,
.upper_margin = 0,
.lower_margin = 0,
.vmode = FB_VMODE_NONINTERLACED,
};
static int ht16k33_display_on(struct ht16k33_priv *priv)
{
uint8_t data = REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON | priv->blink;
return i2c_smbus_write_byte(priv->client, data);
}
static int ht16k33_display_off(struct ht16k33_priv *priv)
{
return i2c_smbus_write_byte(priv->client, REG_DISPLAY_SETUP);
}
static int ht16k33_brightness_set(struct ht16k33_priv *priv,
unsigned int brightness)
{
int err;
if (brightness == 0) {
priv->blink = REG_DISPLAY_SETUP_BLINK_OFF;
return ht16k33_display_off(priv);
}
err = ht16k33_display_on(priv);
if (err)
return err;
return i2c_smbus_write_byte(priv->client,
REG_BRIGHTNESS | (brightness - 1));
}
static int ht16k33_brightness_set_blocking(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct ht16k33_priv *priv = ht16k33_led_to_priv(led_cdev);
return ht16k33_brightness_set(priv, brightness);
}
static int ht16k33_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct ht16k33_priv *priv = ht16k33_led_to_priv(led_cdev);
unsigned int delay;
uint8_t blink;
int err;
if (!*delay_on && !*delay_off) {
blink = REG_DISPLAY_SETUP_BLINK_1HZ;
delay = 1000;
} else if (*delay_on <= 750) {
blink = REG_DISPLAY_SETUP_BLINK_2HZ;
delay = 500;
} else if (*delay_on <= 1500) {
blink = REG_DISPLAY_SETUP_BLINK_1HZ;
delay = 1000;
} else {
blink = REG_DISPLAY_SETUP_BLINK_0HZ5;
delay = 2000;
}
err = i2c_smbus_write_byte(priv->client,
REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON |
blink);
if (err)
return err;
priv->blink = blink;
*delay_on = *delay_off = delay;
return 0;
}
static void ht16k33_fb_queue(struct ht16k33_priv *priv)
{
struct ht16k33_fbdev *fbdev = &priv->fbdev;
schedule_delayed_work(&priv->work, HZ / fbdev->refresh_rate);
}
/*
* This gets the fb data from cache and copies it to ht16k33 display RAM
*/
static void ht16k33_fb_update(struct work_struct *work)
{
struct ht16k33_priv *priv = ht16k33_work_to_priv(work);
struct ht16k33_fbdev *fbdev = &priv->fbdev;
uint8_t *p1, *p2;
int len, pos = 0, first = -1;
p1 = fbdev->cache;
p2 = fbdev->buffer;
/* Search for the first byte with changes */
while (pos < HT16K33_FB_SIZE && first < 0) {
if (*(p1++) - *(p2++))
first = pos;
pos++;
}
/* No changes found */
if (first < 0)
goto requeue;
len = HT16K33_FB_SIZE - first;
p1 = fbdev->cache + HT16K33_FB_SIZE - 1;
p2 = fbdev->buffer + HT16K33_FB_SIZE - 1;
/* Determine i2c transfer length */
while (len > 1) {
if (*(p1--) - *(p2--))
break;
len--;
}
p1 = fbdev->cache + first;
p2 = fbdev->buffer + first;
if (!i2c_smbus_write_i2c_block_data(priv->client, first, len, p2))
memcpy(p1, p2, len);
requeue:
ht16k33_fb_queue(priv);
}
static int ht16k33_initialize(struct ht16k33_priv *priv)
{
uint8_t data[HT16K33_FB_SIZE];
uint8_t byte;
int err;
/* Clear RAM (8 * 16 bits) */
memset(data, 0, sizeof(data));
err = i2c_smbus_write_block_data(priv->client, 0, sizeof(data), data);
if (err)
return err;
/* Turn on internal oscillator */
byte = REG_SYSTEM_SETUP_OSC_ON | REG_SYSTEM_SETUP;
err = i2c_smbus_write_byte(priv->client, byte);
if (err)
return err;
/* Configure INT pin */
byte = REG_ROWINT_SET | REG_ROWINT_SET_INT_ACT_HIGH;
if (priv->client->irq > 0)
byte |= REG_ROWINT_SET_INT_EN;
return i2c_smbus_write_byte(priv->client, byte);
}
static int ht16k33_bl_update_status(struct backlight_device *bl)
{
const int brightness = backlight_get_brightness(bl);
struct ht16k33_priv *priv = bl_get_data(bl);
return ht16k33_brightness_set(priv, brightness);
}
static const struct backlight_ops ht16k33_bl_ops = {
.update_status = ht16k33_bl_update_status,
};
/*
* Blank events will be passed to the actual device handling the backlight when
* we return zero here.
*/
static int ht16k33_blank(int blank, struct fb_info *info)
{
return 0;
}
static int ht16k33_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct ht16k33_priv *priv = info->par;
struct page *pages = virt_to_page(priv->fbdev.buffer);
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
return vm_map_pages_zero(vma, &pages, 1);
}
static const struct fb_ops ht16k33_fb_ops = {
.owner = THIS_MODULE,
__FB_DEFAULT_SYSMEM_OPS_RDWR,
.fb_blank = ht16k33_blank,
__FB_DEFAULT_SYSMEM_OPS_DRAW,
.fb_mmap = ht16k33_mmap,
};
/*
* This gets the keys from keypad and reports it to input subsystem.
* Returns true if a key is pressed.
*/
static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
{
const unsigned short *keycodes = keypad->dev->keycode;
u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
__le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS];
unsigned long bits_changed;
int row, col, code;
int rc;
bool pressed = false;
rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40,
sizeof(data), (u8 *)data);
if (rc != sizeof(data)) {
dev_err(&keypad->client->dev,
"Failed to read key data, rc=%d\n", rc);
return false;
}
for (col = 0; col < keypad->cols; col++) {
new_state[col] = le16_to_cpu(data[col]);
if (new_state[col])
pressed = true;
bits_changed = keypad->last_key_state[col] ^ new_state[col];
for_each_set_bit(row, &bits_changed, BITS_PER_LONG) {
code = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
input_event(keypad->dev, EV_MSC, MSC_SCAN, code);
input_report_key(keypad->dev, keycodes[code],
new_state[col] & BIT(row));
}
}
input_sync(keypad->dev);
memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols);
return pressed;
}
static irqreturn_t ht16k33_keypad_irq_thread(int irq, void *dev)
{
struct ht16k33_keypad *keypad = dev;
do {
wait_event_timeout(keypad->wait, keypad->stopped,
msecs_to_jiffies(keypad->debounce_ms));
if (keypad->stopped)
break;
} while (ht16k33_keypad_scan(keypad));
return IRQ_HANDLED;
}
static int ht16k33_keypad_start(struct input_dev *dev)
{
struct ht16k33_keypad *keypad = input_get_drvdata(dev);
keypad->stopped = false;
mb();
enable_irq(keypad->client->irq);
return 0;
}
static void ht16k33_keypad_stop(struct input_dev *dev)
{
struct ht16k33_keypad *keypad = input_get_drvdata(dev);
keypad->stopped = true;
mb();
wake_up(&keypad->wait);
disable_irq(keypad->client->irq);
}
static void ht16k33_seg7_update(struct work_struct *work)
{
struct ht16k33_priv *priv = ht16k33_work_to_priv(work);
struct linedisp_map *map = priv->linedisp.map;
char *s = priv->linedisp.buf;
uint8_t buf[9];
buf[0] = map_to_seg7(&map->map.seg7, *s++);
buf[1] = 0;
buf[2] = map_to_seg7(&map->map.seg7, *s++);
buf[3] = 0;
buf[4] = 0;
buf[5] = 0;
buf[6] = map_to_seg7(&map->map.seg7, *s++);
buf[7] = 0;
buf[8] = map_to_seg7(&map->map.seg7, *s++);
i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf);
}
static void ht16k33_seg14_update(struct work_struct *work)
{
struct ht16k33_priv *priv = ht16k33_work_to_priv(work);
struct linedisp_map *map = priv->linedisp.map;
char *s = priv->linedisp.buf;
uint8_t buf[8];
put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 0);
put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 2);
put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 4);
put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 6);
i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf);
}
static int ht16k33_linedisp_get_map_type(struct linedisp *linedisp)
{
struct ht16k33_priv *priv = ht16k33_linedisp_to_priv(linedisp);
switch (priv->type) {
case DISP_QUAD_7SEG:
INIT_DELAYED_WORK(&priv->work, ht16k33_seg7_update);
return LINEDISP_MAP_SEG7;
case DISP_QUAD_14SEG:
INIT_DELAYED_WORK(&priv->work, ht16k33_seg14_update);
return LINEDISP_MAP_SEG14;
default:
return -EINVAL;
}
}
static void ht16k33_linedisp_update(struct linedisp *linedisp)
{
struct ht16k33_priv *priv = ht16k33_linedisp_to_priv(linedisp);
schedule_delayed_work(&priv->work, 0);
}
static const struct linedisp_ops ht16k33_linedisp_ops = {
.get_map_type = ht16k33_linedisp_get_map_type,
.update = ht16k33_linedisp_update,
};
static int ht16k33_led_probe(struct device *dev, struct led_classdev *led,
unsigned int brightness)
{
struct led_init_data init_data = {};
int err;
/* The LED is optional */
init_data.fwnode = device_get_named_child_node(dev, "led");
if (!init_data.fwnode)
return 0;
init_data.devicename = "auxdisplay";
init_data.devname_mandatory = true;
led->brightness_set_blocking = ht16k33_brightness_set_blocking;
led->blink_set = ht16k33_blink_set;
led->flags = LED_CORE_SUSPENDRESUME;
led->brightness = brightness;
led->max_brightness = MAX_BRIGHTNESS;
err = devm_led_classdev_register_ext(dev, led, &init_data);
fwnode_handle_put(init_data.fwnode);
if (err)
dev_err(dev, "Failed to register LED\n");
return err;
}
static int ht16k33_keypad_probe(struct i2c_client *client,
struct ht16k33_keypad *keypad)
{
struct device *dev = &client->dev;
u32 rows = HT16K33_MATRIX_KEYPAD_MAX_ROWS;
u32 cols = HT16K33_MATRIX_KEYPAD_MAX_COLS;
int err;
keypad->client = client;
init_waitqueue_head(&keypad->wait);
keypad->dev = devm_input_allocate_device(dev);
if (!keypad->dev)
return -ENOMEM;
input_set_drvdata(keypad->dev, keypad);
keypad->dev->name = DRIVER_NAME"-keypad";
keypad->dev->id.bustype = BUS_I2C;
keypad->dev->open = ht16k33_keypad_start;
keypad->dev->close = ht16k33_keypad_stop;
if (!device_property_read_bool(dev, "linux,no-autorepeat"))
__set_bit(EV_REP, keypad->dev->evbit);
err = device_property_read_u32(dev, "debounce-delay-ms",
&keypad->debounce_ms);
if (err) {
dev_err(dev, "key debounce delay not specified\n");
return err;
}
err = matrix_keypad_parse_properties(dev, &rows, &cols);
if (err)
return err;
if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS ||
cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) {
dev_err(dev, "%u rows or %u cols out of range in DT\n", rows,
cols);
return -ERANGE;
}
keypad->rows = rows;
keypad->cols = cols;
keypad->row_shift = get_count_order(cols);
err = matrix_keypad_build_keymap(NULL, NULL, rows, cols, NULL,
keypad->dev);
if (err) {
dev_err(dev, "failed to build keymap\n");
return err;
}
err = devm_request_threaded_irq(dev, client->irq, NULL,
ht16k33_keypad_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
DRIVER_NAME, keypad);
if (err) {
dev_err(dev, "irq request failed %d, error %d\n", client->irq,
err);
return err;
}
ht16k33_keypad_stop(keypad->dev);
return input_register_device(keypad->dev);
}
static int ht16k33_fbdev_probe(struct device *dev, struct ht16k33_priv *priv,
uint32_t brightness)
{
struct ht16k33_fbdev *fbdev = &priv->fbdev;
struct backlight_device *bl = NULL;
int err;
if (priv->led.dev) {
err = ht16k33_brightness_set(priv, brightness);
if (err)
return err;
} else {
/* backwards compatibility with DT lacking an led subnode */
struct backlight_properties bl_props;
memset(&bl_props, 0, sizeof(struct backlight_properties));
bl_props.type = BACKLIGHT_RAW;
bl_props.max_brightness = MAX_BRIGHTNESS;
bl = devm_backlight_device_register(dev, DRIVER_NAME"-bl", dev,
priv, &ht16k33_bl_ops,
&bl_props);
if (IS_ERR(bl)) {
dev_err(dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = brightness;
ht16k33_bl_update_status(bl);
}
/* Framebuffer (2 bytes per column) */
BUILD_BUG_ON(PAGE_SIZE < HT16K33_FB_SIZE);
fbdev->buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL);
if (!fbdev->buffer)
return -ENOMEM;
fbdev->cache = devm_kmalloc(dev, HT16K33_FB_SIZE, GFP_KERNEL);
if (!fbdev->cache) {
err = -ENOMEM;
goto err_fbdev_buffer;
}
fbdev->info = framebuffer_alloc(0, dev);
if (!fbdev->info) {
err = -ENOMEM;
goto err_fbdev_buffer;
}
err = device_property_read_u32(dev, "refresh-rate-hz",
&fbdev->refresh_rate);
if (err) {
dev_err(dev, "refresh rate not specified\n");
goto err_fbdev_info;
}
fb_bl_default_curve(fbdev->info, 0, MIN_BRIGHTNESS, MAX_BRIGHTNESS);
INIT_DELAYED_WORK(&priv->work, ht16k33_fb_update);
fbdev->info->fbops = &ht16k33_fb_ops;
fbdev->info->flags |= FBINFO_VIRTFB;
fbdev->info->screen_buffer = fbdev->buffer;
fbdev->info->screen_size = HT16K33_FB_SIZE;
fbdev->info->fix = ht16k33_fb_fix;
fbdev->info->var = ht16k33_fb_var;
fbdev->info->bl_dev = bl;
fbdev->info->pseudo_palette = NULL;
fbdev->info->par = priv;
err = register_framebuffer(fbdev->info);
if (err)
goto err_fbdev_info;
ht16k33_fb_queue(priv);
return 0;
err_fbdev_info:
framebuffer_release(fbdev->info);
err_fbdev_buffer:
free_page((unsigned long) fbdev->buffer);
return err;
}
static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv,
uint32_t brightness)
{
struct linedisp *linedisp = &priv->linedisp;
int err;
err = ht16k33_brightness_set(priv, brightness);
if (err)
return err;
return linedisp_register(linedisp, dev, 4, &ht16k33_linedisp_ops);
}
static int ht16k33_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ht16k33_priv *priv;
uint32_t dft_brightness;
int err;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(dev, "i2c_check_functionality error\n");
return -EIO;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
priv->type = (uintptr_t)i2c_get_match_data(client);
i2c_set_clientdata(client, priv);
err = ht16k33_initialize(priv);
if (err)
return err;
err = device_property_read_u32(dev, "default-brightness-level",
&dft_brightness);
if (err) {
dft_brightness = MAX_BRIGHTNESS;
} else if (dft_brightness > MAX_BRIGHTNESS) {
dev_warn(dev,
"invalid default brightness level: %u, using %u\n",
dft_brightness, MAX_BRIGHTNESS);
dft_brightness = MAX_BRIGHTNESS;
}
/* LED */
err = ht16k33_led_probe(dev, &priv->led, dft_brightness);
if (err)
return err;
/* Keypad */
if (client->irq > 0) {
err = ht16k33_keypad_probe(client, &priv->keypad);
if (err)
return err;
}
switch (priv->type) {
case DISP_MATRIX:
/* Frame Buffer Display */
err = ht16k33_fbdev_probe(dev, priv, dft_brightness);
break;
case DISP_QUAD_7SEG:
case DISP_QUAD_14SEG:
/* Segment Display */
err = ht16k33_seg_probe(dev, priv, dft_brightness);
break;
default:
return -EINVAL;
}
return err;
}
static void ht16k33_remove(struct i2c_client *client)
{
struct ht16k33_priv *priv = i2c_get_clientdata(client);
struct ht16k33_fbdev *fbdev = &priv->fbdev;
cancel_delayed_work_sync(&priv->work);
switch (priv->type) {
case DISP_MATRIX:
unregister_framebuffer(fbdev->info);
framebuffer_release(fbdev->info);
free_page((unsigned long)fbdev->buffer);
break;
case DISP_QUAD_7SEG:
case DISP_QUAD_14SEG:
linedisp_unregister(&priv->linedisp);
break;
default:
break;
}
}
static const struct i2c_device_id ht16k33_i2c_match[] = {
{ "3108", DISP_QUAD_7SEG },
{ "3130", DISP_QUAD_14SEG },
{ "ht16k33", DISP_MATRIX },
{ }
};
MODULE_DEVICE_TABLE(i2c, ht16k33_i2c_match);
static const struct of_device_id ht16k33_of_match[] = {
{
/* 0.56" 4-Digit 7-Segment FeatherWing Display (Red) */
.compatible = "adafruit,3108", .data = (void *)DISP_QUAD_7SEG,
}, {
/* 0.54" Quad Alphanumeric FeatherWing Display (Red) */
.compatible = "adafruit,3130", .data = (void *)DISP_QUAD_14SEG,
}, {
/* Generic, assumed Dot-Matrix Display */
.compatible = "holtek,ht16k33", .data = (void *)DISP_MATRIX,
},
{ }
};
MODULE_DEVICE_TABLE(of, ht16k33_of_match);
static struct i2c_driver ht16k33_driver = {
.probe = ht16k33_probe,
.remove = ht16k33_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = ht16k33_of_match,
},
.id_table = ht16k33_i2c_match,
};
module_i2c_driver(ht16k33_driver);
MODULE_DESCRIPTION("Holtek HT16K33 driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("LINEDISP");
MODULE_AUTHOR("Robin van der Gracht <robin@protonic.nl>");
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include "line-display.h"
struct img_ascii_lcd_ctx;
/**
* struct img_ascii_lcd_config - Configuration information about an LCD model
* @num_chars: the number of characters the LCD can display
* @external_regmap: true if registers are in a system controller, else false
* @ops: character line display operations
*/
struct img_ascii_lcd_config {
unsigned int num_chars;
bool external_regmap;
const struct linedisp_ops ops;
};
/**
* struct img_ascii_lcd_ctx - Private data structure
* @linedisp: line display structure
* @base: the base address of the LCD registers
* @regmap: the regmap through which LCD registers are accessed
* @offset: the offset within regmap to the start of the LCD registers
*/
struct img_ascii_lcd_ctx {
struct linedisp linedisp;
union {
void __iomem *base;
struct regmap *regmap;
};
u32 offset;
};
/*
* MIPS Boston development board
*/
static void boston_update(struct linedisp *linedisp)
{
struct img_ascii_lcd_ctx *ctx =
container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
ulong val;
#if BITS_PER_LONG == 64
val = *((u64 *)&linedisp->buf[0]);
__raw_writeq(val, ctx->base);
#elif BITS_PER_LONG == 32
val = *((u32 *)&linedisp->buf[0]);
__raw_writel(val, ctx->base);
val = *((u32 *)&linedisp->buf[4]);
__raw_writel(val, ctx->base + 4);
#else
# error Not 32 or 64 bit
#endif
}
static const struct img_ascii_lcd_config boston_config = {
.num_chars = 8,
.ops = {
.update = boston_update,
},
};
/*
* MIPS Malta development board
*/
static void malta_update(struct linedisp *linedisp)
{
struct img_ascii_lcd_ctx *ctx =
container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
unsigned int i;
int err = 0;
for (i = 0; i < linedisp->num_chars; i++) {
err = regmap_write(ctx->regmap,
ctx->offset + (i * 8), linedisp->buf[i]);
if (err)
break;
}
if (unlikely(err))
pr_err_ratelimited("Failed to update LCD display: %d\n", err);
}
static const struct img_ascii_lcd_config malta_config = {
.num_chars = 8,
.external_regmap = true,
.ops = {
.update = malta_update,
},
};
/*
* MIPS SEAD3 development board
*/
enum {
SEAD3_REG_LCD_CTRL = 0x00,
#define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7)
SEAD3_REG_LCD_DATA = 0x08,
SEAD3_REG_CPLD_STATUS = 0x10,
#define SEAD3_REG_CPLD_STATUS_BUSY BIT(0)
SEAD3_REG_CPLD_DATA = 0x18,
#define SEAD3_REG_CPLD_DATA_BUSY BIT(7)
};
static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx)
{
unsigned int status;
int err;
do {
err = regmap_read(ctx->regmap,
ctx->offset + SEAD3_REG_CPLD_STATUS,
&status);
if (err)
return err;
} while (status & SEAD3_REG_CPLD_STATUS_BUSY);
return 0;
}
static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx)
{
unsigned int cpld_data;
int err;
err = sead3_wait_sm_idle(ctx);
if (err)
return err;
do {
err = regmap_read(ctx->regmap,
ctx->offset + SEAD3_REG_LCD_CTRL,
&cpld_data);
if (err)
return err;
err = sead3_wait_sm_idle(ctx);
if (err)
return err;
err = regmap_read(ctx->regmap,
ctx->offset + SEAD3_REG_CPLD_DATA,
&cpld_data);
if (err)
return err;
} while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY);
return 0;
}
static void sead3_update(struct linedisp *linedisp)
{
struct img_ascii_lcd_ctx *ctx =
container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
unsigned int i;
int err = 0;
for (i = 0; i < linedisp->num_chars; i++) {
err = sead3_wait_lcd_idle(ctx);
if (err)
break;
err = regmap_write(ctx->regmap,
ctx->offset + SEAD3_REG_LCD_CTRL,
SEAD3_REG_LCD_CTRL_SETDRAM | i);
if (err)
break;
err = sead3_wait_lcd_idle(ctx);
if (err)
break;
err = regmap_write(ctx->regmap,
ctx->offset + SEAD3_REG_LCD_DATA,
linedisp->buf[i]);
if (err)
break;
}
if (unlikely(err))
pr_err_ratelimited("Failed to update LCD display: %d\n", err);
}
static const struct img_ascii_lcd_config sead3_config = {
.num_chars = 16,
.external_regmap = true,
.ops = {
.update = sead3_update,
},
};
static const struct of_device_id img_ascii_lcd_matches[] = {
{ .compatible = "img,boston-lcd", .data = &boston_config },
{ .compatible = "mti,malta-lcd", .data = &malta_config },
{ .compatible = "mti,sead3-lcd", .data = &sead3_config },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
/**
* img_ascii_lcd_probe() - probe an LCD display device
* @pdev: the LCD platform device
*
* Probe an LCD display device, ensuring that we have the required resources in
* order to access the LCD & setting up private data as well as sysfs files.
*
* Return: 0 on success, else -ERRNO
*/
static int img_ascii_lcd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct img_ascii_lcd_config *cfg = device_get_match_data(dev);
struct img_ascii_lcd_ctx *ctx;
int err;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
if (cfg->external_regmap) {
ctx->regmap = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(ctx->regmap))
return PTR_ERR(ctx->regmap);
if (of_property_read_u32(dev->of_node, "offset", &ctx->offset))
return -EINVAL;
} else {
ctx->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctx->base))
return PTR_ERR(ctx->base);
}
err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops);
if (err)
return err;
/* for backwards compatibility */
err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
&ctx->linedisp.dev.kobj,
"message", NULL);
if (err)
goto err_unregister;
platform_set_drvdata(pdev, ctx);
return 0;
err_unregister:
linedisp_unregister(&ctx->linedisp);
return err;
}
/**
* img_ascii_lcd_remove() - remove an LCD display device
* @pdev: the LCD platform device
*
* Remove an LCD display device, freeing private resources & ensuring that the
* driver stops using the LCD display registers.
*/
static void img_ascii_lcd_remove(struct platform_device *pdev)
{
struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
sysfs_remove_link(&pdev->dev.kobj, "message");
linedisp_unregister(&ctx->linedisp);
}
static struct platform_driver img_ascii_lcd_driver = {
.driver = {
.name = "img-ascii-lcd",
.of_match_table = img_ascii_lcd_matches,
},
.probe = img_ascii_lcd_probe,
.remove = img_ascii_lcd_remove,
};
module_platform_driver(img_ascii_lcd_driver);
MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("LINEDISP");
+171
View File
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Filename: ks0108.c
* Version: 0.1.0
* Description: ks0108 LCD Controller driver
* Depends: parport
*
* Author: Copyright (C) Miguel Ojeda <ojeda@kernel.org>
* Date: 2006-10-31
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/parport.h>
#include <linux/ks0108.h>
#define KS0108_NAME "ks0108"
/*
* Module Parameters
*/
static unsigned int ks0108_port = CONFIG_KS0108_PORT;
module_param(ks0108_port, uint, 0444);
MODULE_PARM_DESC(ks0108_port, "Parallel port where the LCD is connected");
static unsigned int ks0108_delay = CONFIG_KS0108_DELAY;
module_param(ks0108_delay, uint, 0444);
MODULE_PARM_DESC(ks0108_delay, "Delay between each control writing (microseconds)");
/*
* Device
*/
static struct parport *ks0108_parport;
static struct pardevice *ks0108_pardevice;
/*
* ks0108 Exported Commands (don't lock)
*
* You _should_ lock in the top driver: This functions _should not_
* get race conditions in any way. Locking for each byte here would be
* so slow and useless.
*
* There are not bit definitions because they are not flags,
* just arbitrary combinations defined by the documentation for each
* function in the ks0108 LCD controller. If you want to know what means
* a specific combination, look at the function's name.
*
* The ks0108_writecontrol bits need to be reverted ^(0,1,3) because
* the parallel port also revert them using a "not" logic gate.
*/
#define bit(n) (((unsigned char)1)<<(n))
void ks0108_writedata(unsigned char byte)
{
parport_write_data(ks0108_parport, byte);
}
void ks0108_writecontrol(unsigned char byte)
{
udelay(ks0108_delay);
parport_write_control(ks0108_parport, byte ^ (bit(0) | bit(1) | bit(3)));
}
void ks0108_displaystate(unsigned char state)
{
ks0108_writedata((state ? bit(0) : 0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5));
}
void ks0108_startline(unsigned char startline)
{
ks0108_writedata(min_t(unsigned char, startline, 63) | bit(6) |
bit(7));
}
void ks0108_address(unsigned char address)
{
ks0108_writedata(min_t(unsigned char, address, 63) | bit(6));
}
void ks0108_page(unsigned char page)
{
ks0108_writedata(min_t(unsigned char, page, 7) | bit(3) | bit(4) |
bit(5) | bit(7));
}
EXPORT_SYMBOL_GPL(ks0108_writedata);
EXPORT_SYMBOL_GPL(ks0108_writecontrol);
EXPORT_SYMBOL_GPL(ks0108_displaystate);
EXPORT_SYMBOL_GPL(ks0108_startline);
EXPORT_SYMBOL_GPL(ks0108_address);
EXPORT_SYMBOL_GPL(ks0108_page);
/*
* Is the module inited?
*/
static unsigned char ks0108_inited;
unsigned char ks0108_isinited(void)
{
return ks0108_inited;
}
EXPORT_SYMBOL_GPL(ks0108_isinited);
static void ks0108_parport_attach(struct parport *port)
{
struct pardev_cb ks0108_cb;
if (port->base != ks0108_port)
return;
memset(&ks0108_cb, 0, sizeof(ks0108_cb));
ks0108_cb.flags = PARPORT_DEV_EXCL;
ks0108_pardevice = parport_register_dev_model(port, KS0108_NAME,
&ks0108_cb, 0);
if (!ks0108_pardevice) {
pr_err("ERROR: parport didn't register new device\n");
return;
}
if (parport_claim(ks0108_pardevice)) {
pr_err("could not claim access to parport %i. Aborting.\n",
ks0108_port);
goto err_unreg_device;
}
ks0108_parport = port;
ks0108_inited = 1;
return;
err_unreg_device:
parport_unregister_device(ks0108_pardevice);
ks0108_pardevice = NULL;
}
static void ks0108_parport_detach(struct parport *port)
{
if (port->base != ks0108_port)
return;
if (!ks0108_pardevice) {
pr_err("%s: already unregistered.\n", KS0108_NAME);
return;
}
parport_release(ks0108_pardevice);
parport_unregister_device(ks0108_pardevice);
ks0108_pardevice = NULL;
ks0108_parport = NULL;
}
/*
* Module Init & Exit
*/
static struct parport_driver ks0108_parport_driver = {
.name = "ks0108",
.match_port = ks0108_parport_attach,
.detach = ks0108_parport_detach,
};
module_parport_driver(ks0108_parport_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Miguel Ojeda <ojeda@kernel.org>");
MODULE_DESCRIPTION("ks0108 LCD Controller driver");
+381
View File
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Console driver for LCD2S 4x20 character displays connected through i2c.
* The display also has a SPI interface, but the driver does not support
* this yet.
*
* This is a driver allowing you to use a LCD2S 4x20 from Modtronix
* engineering as auxdisplay character device.
*
* (C) 2019 by Lemonage Software GmbH
* Author: Lars Pöschel <poeschel@lemonage.de>
* All rights reserved.
*/
#include <linux/hex.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "charlcd.h"
#define LCD2S_CMD_CUR_MOVES_FWD 0x09
#define LCD2S_CMD_CUR_BLINK_OFF 0x10
#define LCD2S_CMD_CUR_UL_OFF 0x11
#define LCD2S_CMD_DISPLAY_OFF 0x12
#define LCD2S_CMD_CUR_BLINK_ON 0x18
#define LCD2S_CMD_CUR_UL_ON 0x19
#define LCD2S_CMD_DISPLAY_ON 0x1a
#define LCD2S_CMD_BACKLIGHT_OFF 0x20
#define LCD2S_CMD_BACKLIGHT_ON 0x28
#define LCD2S_CMD_WRITE 0x80
#define LCD2S_CMD_MOV_CUR_RIGHT 0x83
#define LCD2S_CMD_MOV_CUR_LEFT 0x84
#define LCD2S_CMD_SHIFT_RIGHT 0x85
#define LCD2S_CMD_SHIFT_LEFT 0x86
#define LCD2S_CMD_SHIFT_UP 0x87
#define LCD2S_CMD_SHIFT_DOWN 0x88
#define LCD2S_CMD_CUR_ADDR 0x89
#define LCD2S_CMD_CUR_POS 0x8a
#define LCD2S_CMD_CUR_RESET 0x8b
#define LCD2S_CMD_CLEAR 0x8c
#define LCD2S_CMD_DEF_CUSTOM_CHAR 0x92
#define LCD2S_CMD_READ_STATUS 0xd0
#define LCD2S_CHARACTER_SIZE 8
#define LCD2S_STATUS_BUF_MASK 0x7f
struct lcd2s_data {
struct i2c_client *i2c;
struct charlcd *charlcd;
};
static s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
{
s32 status;
status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
if (status < 0)
return status;
while ((status & LCD2S_STATUS_BUF_MASK) < count) {
mdelay(1);
status = i2c_smbus_read_byte_data(client,
LCD2S_CMD_READ_STATUS);
if (status < 0)
return status;
}
return 0;
}
static int lcd2s_i2c_master_send(const struct i2c_client *client,
const char *buf, int count)
{
s32 status;
status = lcd2s_wait_buf_free(client, count);
if (status < 0)
return status;
return i2c_master_send(client, buf, count);
}
static int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
s32 status;
status = lcd2s_wait_buf_free(client, 1);
if (status < 0)
return status;
return i2c_smbus_write_byte(client, value);
}
static int lcd2s_print(struct charlcd *lcd, int c)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
u8 buf[2] = { LCD2S_CMD_WRITE, c };
int ret;
ret = lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
if (ret < 0)
return ret;
if (ret != sizeof(buf))
return -EIO;
return 0;
}
static int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
u8 buf[3] = { LCD2S_CMD_CUR_POS, y + 1, x + 1 };
int ret;
ret = lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
if (ret < 0)
return ret;
if (ret != sizeof(buf))
return -EIO;
return 0;
}
static int lcd2s_home(struct charlcd *lcd)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
return 0;
}
static int lcd2s_init_display(struct charlcd *lcd)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
/* turn everything off, but display on */
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_MOVES_FWD);
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
return 0;
}
static int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (dir == CHARLCD_SHIFT_LEFT)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);
return 0;
}
static int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (dir == CHARLCD_SHIFT_LEFT)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);
return 0;
}
static void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (on)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
}
static int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (on)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);
return 0;
}
static int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (on)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
return 0;
}
static int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
if (on)
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
else
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
return 0;
}
static int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
{
return 0;
}
static int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
{
return 0;
}
/*
* Generator: LGcxxxxx...xx; must have <c> between '0' and '7',
* representing the numerical ASCII code of the redefined character,
* and <xx...xx> a sequence of 16 hex digits representing 8 bytes
* for each character. Most LCDs will only use 5 lower bits of
* the 7 first bytes.
*/
static int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
u8 value;
int shift, i;
if (!strchr(esc, ';'))
return 0;
esc++;
buf[1] = *(esc++) - '0';
if (buf[1] > 7)
return 1;
i = 2;
shift = 0;
value = 0;
while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
int half;
shift ^= 4;
half = hex_to_bin(*esc++);
if (half < 0)
continue;
value |= half << shift;
if (shift == 0) {
buf[i++] = value;
value = 0;
}
}
lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
return 1;
}
static int lcd2s_clear_display(struct charlcd *lcd)
{
struct lcd2s_data *lcd2s = lcd->drvdata;
/* This implicitly sets cursor to first row and column */
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
return 0;
}
static const struct charlcd_ops lcd2s_ops = {
.print = lcd2s_print,
.backlight = lcd2s_backlight,
.gotoxy = lcd2s_gotoxy,
.home = lcd2s_home,
.clear_display = lcd2s_clear_display,
.init_display = lcd2s_init_display,
.shift_cursor = lcd2s_shift_cursor,
.shift_display = lcd2s_shift_display,
.display = lcd2s_display,
.cursor = lcd2s_cursor,
.blink = lcd2s_blink,
.fontsize = lcd2s_fontsize,
.lines = lcd2s_lines,
.redefine_char = lcd2s_redefine_char,
};
static int lcd2s_i2c_probe(struct i2c_client *i2c)
{
struct charlcd *lcd;
struct lcd2s_data *lcd2s;
int err;
if (!i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
return -EIO;
/* Test, if the display is responding */
err = lcd2s_i2c_smbus_write_byte(i2c, LCD2S_CMD_DISPLAY_OFF);
if (err < 0)
return err;
lcd = charlcd_alloc(sizeof(*lcd2s));
if (!lcd)
return -ENOMEM;
lcd->ops = &lcd2s_ops;
lcd2s = lcd->drvdata;
lcd2s->i2c = i2c;
lcd2s->charlcd = lcd;
/* Required properties */
err = device_property_read_u32(&i2c->dev, "display-height-chars",
&lcd->height);
if (err)
goto fail1;
err = device_property_read_u32(&i2c->dev, "display-width-chars",
&lcd->width);
if (err)
goto fail1;
err = charlcd_register(lcd2s->charlcd);
if (err)
goto fail1;
i2c_set_clientdata(i2c, lcd2s);
return 0;
fail1:
charlcd_free(lcd2s->charlcd);
return err;
}
static void lcd2s_i2c_remove(struct i2c_client *i2c)
{
struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);
charlcd_unregister(lcd2s->charlcd);
charlcd_free(lcd2s->charlcd);
}
static const struct i2c_device_id lcd2s_i2c_id[] = {
{ "lcd2s" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);
static const struct of_device_id lcd2s_of_table[] = {
{ .compatible = "modtronix,lcd2s" },
{ }
};
MODULE_DEVICE_TABLE(of, lcd2s_of_table);
static struct i2c_driver lcd2s_i2c_driver = {
.driver = {
.name = "lcd2s",
.of_match_table = lcd2s_of_table,
},
.probe = lcd2s_i2c_probe,
.remove = lcd2s_i2c_remove,
.id_table = lcd2s_i2c_id,
};
module_i2c_driver(lcd2s_i2c_driver);
MODULE_DESCRIPTION("LCD2S character display driver");
MODULE_AUTHOR("Lars Poeschel");
MODULE_LICENSE("GPL");
@@ -0,0 +1,596 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Character line display core support
*
* Copyright (C) 2016 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*
* Copyright (C) 2021 Glider bv
* Copyright (C) 2025 Jean-François Lessard
*/
#ifndef CONFIG_PANEL_BOOT_MESSAGE
#include <generated/utsrelease.h>
#endif
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/idr.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/timer.h>
#include <linux/map_to_7segment.h>
#include <linux/map_to_14segment.h>
#include "line-display.h"
#define DEFAULT_SCROLL_RATE (HZ / 2)
/**
* struct linedisp_attachment - Holds the device to linedisp mapping
* @list: List entry for the linedisp_attachments list
* @device: Pointer to the device where linedisp attributes are added
* @linedisp: Pointer to the linedisp mapped to the device
* @direct: true for directly attached device using linedisp_attach(),
* false for child registered device using linedisp_register()
*/
struct linedisp_attachment {
struct list_head list;
struct device *device;
struct linedisp *linedisp;
bool direct;
};
static LIST_HEAD(linedisp_attachments);
static DEFINE_SPINLOCK(linedisp_attachments_lock);
static int create_attachment(struct device *dev, struct linedisp *linedisp, bool direct)
{
struct linedisp_attachment *attachment;
attachment = kzalloc_obj(*attachment);
if (!attachment)
return -ENOMEM;
attachment->device = dev;
attachment->linedisp = linedisp;
attachment->direct = direct;
guard(spinlock)(&linedisp_attachments_lock);
list_add(&attachment->list, &linedisp_attachments);
return 0;
}
static struct linedisp *delete_attachment(struct device *dev, bool direct)
{
struct linedisp_attachment *attachment;
struct linedisp *linedisp;
guard(spinlock)(&linedisp_attachments_lock);
list_for_each_entry(attachment, &linedisp_attachments, list) {
if (attachment->device == dev &&
attachment->direct == direct)
break;
}
if (list_entry_is_head(attachment, &linedisp_attachments, list))
return NULL;
linedisp = attachment->linedisp;
list_del(&attachment->list);
kfree(attachment);
return linedisp;
}
static struct linedisp *to_linedisp(struct device *dev)
{
struct linedisp_attachment *attachment;
guard(spinlock)(&linedisp_attachments_lock);
list_for_each_entry(attachment, &linedisp_attachments, list) {
if (attachment->device == dev)
break;
}
if (list_entry_is_head(attachment, &linedisp_attachments, list))
return NULL;
return attachment->linedisp;
}
static inline bool should_scroll(struct linedisp *linedisp)
{
return linedisp->message_len > linedisp->num_chars && linedisp->scroll_rate;
}
/**
* linedisp_scroll() - scroll the display by a character
* @t: really a pointer to the private data structure
*
* Scroll the current message along the display by one character, rearming the
* timer if required.
*/
static void linedisp_scroll(struct timer_list *t)
{
struct linedisp *linedisp = timer_container_of(linedisp, t, timer);
unsigned int i, ch = linedisp->scroll_pos;
unsigned int num_chars = linedisp->num_chars;
/* update the current message string */
for (i = 0; i < num_chars;) {
/* copy as many characters from the string as possible */
for (; i < num_chars && ch < linedisp->message_len; i++, ch++)
linedisp->buf[i] = linedisp->message[ch];
/* wrap around to the start of the string */
ch = 0;
}
/* update the display */
linedisp->ops->update(linedisp);
/* move on to the next character */
linedisp->scroll_pos++;
linedisp->scroll_pos %= linedisp->message_len;
/* rearm the timer */
mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate);
}
/**
* linedisp_display() - set the message to be displayed
* @linedisp: pointer to the private data structure
* @msg: the message to display
* @count: length of msg, or -1
*
* Display a new message @msg on the display. @msg can be longer than the
* number of characters the display can display, in which case it will begin
* scrolling across the display.
*
* Return: 0 on success, -ENOMEM on memory allocation failure
*/
static int linedisp_display(struct linedisp *linedisp, const char *msg,
ssize_t count)
{
char *new_msg;
/* stop the scroll timer */
timer_delete_sync(&linedisp->timer);
if (count == -1)
count = strlen(msg);
/* if the string ends with a newline, trim it */
if (count && msg[count - 1] == '\n')
count--;
if (!count) {
/* Clear the display */
kfree(linedisp->message);
linedisp->message = NULL;
linedisp->message_len = 0;
memset(linedisp->buf, ' ', linedisp->num_chars);
linedisp->ops->update(linedisp);
return 0;
}
new_msg = kmemdup_nul(msg, count, GFP_KERNEL);
if (!new_msg)
return -ENOMEM;
kfree(linedisp->message);
linedisp->message = new_msg;
linedisp->message_len = count;
linedisp->scroll_pos = 0;
if (should_scroll(linedisp)) {
/* display scrolling message */
linedisp_scroll(&linedisp->timer);
} else {
/* display static message */
memset(linedisp->buf, ' ', linedisp->num_chars);
memcpy(linedisp->buf, linedisp->message,
umin(linedisp->num_chars, linedisp->message_len));
linedisp->ops->update(linedisp);
}
return 0;
}
/**
* message_show() - read message via sysfs
* @dev: the display device
* @attr: the display message attribute
* @buf: the buffer to read the message into
*
* Read the current message being displayed or scrolled across the display into
* @buf, for reads from sysfs.
*
* Return: the number of characters written to @buf
*/
static ssize_t message_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct linedisp *linedisp = to_linedisp(dev);
return sysfs_emit(buf, "%s\n", linedisp->message);
}
/**
* message_store() - write a new message via sysfs
* @dev: the display device
* @attr: the display message attribute
* @buf: the buffer containing the new message
* @count: the size of the message in @buf
*
* Write a new message to display or scroll across the display from sysfs.
*
* Return: the size of the message on success, else -ERRNO
*/
static ssize_t message_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct linedisp *linedisp = to_linedisp(dev);
int err;
err = linedisp_display(linedisp, buf, count);
return err ?: count;
}
static DEVICE_ATTR_RW(message);
static ssize_t num_chars_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct linedisp *linedisp = to_linedisp(dev);
return sysfs_emit(buf, "%u\n", linedisp->num_chars);
}
static DEVICE_ATTR_RO(num_chars);
static ssize_t scroll_step_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct linedisp *linedisp = to_linedisp(dev);
return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate));
}
static ssize_t scroll_step_ms_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct linedisp *linedisp = to_linedisp(dev);
unsigned int ms;
int err;
err = kstrtouint(buf, 10, &ms);
if (err)
return err;
timer_delete_sync(&linedisp->timer);
linedisp->scroll_rate = msecs_to_jiffies(ms);
if (should_scroll(linedisp))
linedisp_scroll(&linedisp->timer);
return count;
}
static DEVICE_ATTR_RW(scroll_step_ms);
static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct linedisp *linedisp = to_linedisp(dev);
struct linedisp_map *map = linedisp->map;
memcpy(buf, &map->map, map->size);
return map->size;
}
static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct linedisp *linedisp = to_linedisp(dev);
struct linedisp_map *map = linedisp->map;
if (count != map->size)
return -EINVAL;
memcpy(&map->map, buf, count);
return count;
}
static const SEG7_DEFAULT_MAP(initial_map_seg7);
static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store);
static const SEG14_DEFAULT_MAP(initial_map_seg14);
static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store);
static struct attribute *linedisp_attrs[] = {
&dev_attr_message.attr,
&dev_attr_num_chars.attr,
&dev_attr_scroll_step_ms.attr,
&dev_attr_map_seg7.attr,
&dev_attr_map_seg14.attr,
NULL
};
static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct linedisp *linedisp = to_linedisp(dev);
struct linedisp_map *map = linedisp->map;
umode_t mode = attr->mode;
if (attr == &dev_attr_map_seg7.attr) {
if (!map)
return 0;
if (map->type != LINEDISP_MAP_SEG7)
return 0;
}
if (attr == &dev_attr_map_seg14.attr) {
if (!map)
return 0;
if (map->type != LINEDISP_MAP_SEG14)
return 0;
}
return mode;
};
static const struct attribute_group linedisp_group = {
.is_visible = linedisp_attr_is_visible,
.attrs = linedisp_attrs,
};
__ATTRIBUTE_GROUPS(linedisp);
static DEFINE_IDA(linedisp_id);
static void linedisp_release(struct device *dev)
{
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
kfree(linedisp->map);
kfree(linedisp->message);
kfree(linedisp->buf);
ida_free(&linedisp_id, linedisp->id);
}
static const struct device_type linedisp_type = {
.groups = linedisp_groups,
.release = linedisp_release,
};
static int linedisp_init_map(struct linedisp *linedisp)
{
struct linedisp_map *map;
int err;
if (!linedisp->ops->get_map_type)
return 0;
err = linedisp->ops->get_map_type(linedisp);
if (err < 0)
return err;
map = kmalloc_obj(*map);
if (!map)
return -ENOMEM;
map->type = err;
/* assign initial mapping */
switch (map->type) {
case LINEDISP_MAP_SEG7:
map->map.seg7 = initial_map_seg7;
map->size = sizeof(map->map.seg7);
break;
case LINEDISP_MAP_SEG14:
map->map.seg14 = initial_map_seg14;
map->size = sizeof(map->map.seg14);
break;
default:
kfree(map);
return -EINVAL;
}
linedisp->map = map;
return 0;
}
#ifdef CONFIG_PANEL_BOOT_MESSAGE
#define LINEDISP_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
#else
#define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " "
#endif
/**
* linedisp_attach - attach a character line display
* @linedisp: pointer to character line display structure
* @dev: pointer of the device to attach to
* @num_chars: the number of characters that can be displayed
* @ops: character line display operations
*
* Directly attach the line-display sysfs attributes to the passed device.
* The caller is responsible for calling linedisp_detach() to release resources
* after use.
*
* Return: zero on success, else a negative error code.
*/
int linedisp_attach(struct linedisp *linedisp, struct device *dev,
unsigned int num_chars, const struct linedisp_ops *ops)
{
int err;
memset(linedisp, 0, sizeof(*linedisp));
linedisp->ops = ops;
linedisp->num_chars = num_chars;
linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL);
if (!linedisp->buf)
return -ENOMEM;
/* initialise a character mapping, if required */
err = linedisp_init_map(linedisp);
if (err)
goto out_free_buf;
/* initialise a timer for scrolling the message */
timer_setup(&linedisp->timer, linedisp_scroll, 0);
err = create_attachment(dev, linedisp, true);
if (err)
goto out_del_timer;
/* display a default message */
err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1);
if (err)
goto out_del_attach;
/* add attribute groups to target device */
err = device_add_groups(dev, linedisp_groups);
if (err)
goto out_del_attach;
return 0;
out_del_attach:
delete_attachment(dev, true);
out_del_timer:
timer_delete_sync(&linedisp->timer);
out_free_buf:
kfree(linedisp->buf);
return err;
}
EXPORT_SYMBOL_NS_GPL(linedisp_attach, "LINEDISP");
/**
* linedisp_detach - detach a character line display
* @dev: pointer of the device to detach from, that was previously
* attached with linedisp_attach()
*/
void linedisp_detach(struct device *dev)
{
struct linedisp *linedisp;
linedisp = delete_attachment(dev, true);
if (!linedisp)
return;
timer_delete_sync(&linedisp->timer);
device_remove_groups(dev, linedisp_groups);
kfree(linedisp->map);
kfree(linedisp->message);
kfree(linedisp->buf);
}
EXPORT_SYMBOL_NS_GPL(linedisp_detach, "LINEDISP");
/**
* linedisp_register - register a character line display
* @linedisp: pointer to character line display structure
* @parent: parent device
* @num_chars: the number of characters that can be displayed
* @ops: character line display operations
*
* Register the line-display sysfs attributes to a new device named
* "linedisp.N" added to the passed parent device.
* The caller is responsible for calling linedisp_unregister() to release
* resources after use.
*
* Return: zero on success, else a negative error code.
*/
int linedisp_register(struct linedisp *linedisp, struct device *parent,
unsigned int num_chars, const struct linedisp_ops *ops)
{
int err;
memset(linedisp, 0, sizeof(*linedisp));
linedisp->dev.parent = parent;
linedisp->dev.type = &linedisp_type;
linedisp->ops = ops;
linedisp->num_chars = num_chars;
linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
err = ida_alloc(&linedisp_id, GFP_KERNEL);
if (err < 0)
return err;
linedisp->id = err;
device_initialize(&linedisp->dev);
dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id);
err = -ENOMEM;
linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL);
if (!linedisp->buf)
goto out_put_device;
/* initialise a character mapping, if required */
err = linedisp_init_map(linedisp);
if (err)
goto out_put_device;
/* initialise a timer for scrolling the message */
timer_setup(&linedisp->timer, linedisp_scroll, 0);
err = create_attachment(&linedisp->dev, linedisp, false);
if (err)
goto out_del_timer;
/* display a default message */
err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1);
if (err)
goto out_del_attach;
err = device_add(&linedisp->dev);
if (err)
goto out_del_attach;
return 0;
out_del_attach:
delete_attachment(&linedisp->dev, false);
out_del_timer:
timer_delete_sync(&linedisp->timer);
out_put_device:
put_device(&linedisp->dev);
return err;
}
EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP");
/**
* linedisp_unregister - unregister a character line display
* @linedisp: pointer to character line display structure registered previously
* with linedisp_register()
*/
void linedisp_unregister(struct linedisp *linedisp)
{
device_del(&linedisp->dev);
delete_attachment(&linedisp->dev, false);
timer_delete_sync(&linedisp->timer);
put_device(&linedisp->dev);
}
EXPORT_SYMBOL_NS_GPL(linedisp_unregister, "LINEDISP");
MODULE_DESCRIPTION("Character line display core support");
MODULE_LICENSE("GPL");
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Character line display core support
*
* Copyright (C) 2016 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*
* Copyright (C) 2021 Glider bv
* Copyright (C) 2025 Jean-François Lessard
*/
#ifndef _LINEDISP_H
#define _LINEDISP_H
#include <linux/device.h>
#include <linux/timer_types.h>
#include <linux/map_to_7segment.h>
#include <linux/map_to_14segment.h>
struct linedisp;
/**
* enum linedisp_map_type - type of the character mapping
* @LINEDISP_MAP_SEG7: Map characters to 7 segment display
* @LINEDISP_MAP_SEG14: Map characters to 14 segment display
*/
enum linedisp_map_type {
LINEDISP_MAP_SEG7,
LINEDISP_MAP_SEG14,
};
/**
* struct linedisp_map - character mapping
* @type: type of the character mapping
* @map: conversion character mapping
* @size: size of the @map
*/
struct linedisp_map {
enum linedisp_map_type type;
union {
struct seg7_conversion_map seg7;
struct seg14_conversion_map seg14;
} map;
unsigned int size;
};
/**
* struct linedisp_ops - character line display operations
* @get_map_type: Function called to get the character mapping, if required
* @update: Function called to update the display. This must not sleep!
*/
struct linedisp_ops {
int (*get_map_type)(struct linedisp *linedisp);
void (*update)(struct linedisp *linedisp);
};
/**
* struct linedisp - character line display private data structure
* @dev: the line display device
* @timer: timer used to implement scrolling
* @ops: character line display operations
* @buf: pointer to the buffer for the string currently displayed
* @message: the full message to display or scroll on the display
* @num_chars: the number of characters that can be displayed
* @message_len: the length of the @message string
* @scroll_pos: index of the first character of @message currently displayed
* @scroll_rate: scroll interval in jiffies
* @id: instance id of this display
*/
struct linedisp {
struct device dev;
struct timer_list timer;
const struct linedisp_ops *ops;
struct linedisp_map *map;
char *buf;
char *message;
unsigned int num_chars;
unsigned int message_len;
unsigned int scroll_pos;
unsigned int scroll_rate;
unsigned int id;
};
int linedisp_attach(struct linedisp *linedisp, struct device *dev,
unsigned int num_chars, const struct linedisp_ops *ops);
void linedisp_detach(struct device *dev);
int linedisp_register(struct linedisp *linedisp, struct device *parent,
unsigned int num_chars, const struct linedisp_ops *ops);
void linedisp_unregister(struct linedisp *linedisp);
#endif /* LINEDISP_H */
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MAX6958/6959 7-segment LED display controller
* Datasheet:
* https://www.analog.com/media/en/technical-documentation/data-sheets/MAX6958-MAX6959.pdf
*
* Copyright (c) 2024, Intel Corporation.
* Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
*/
#include <linux/array_size.h>
#include <linux/bitrev.h>
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/device/devres.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/map_to_7segment.h>
#include "line-display.h"
/* Registers */
#define REG_DECODE_MODE 0x01
#define REG_INTENSITY 0x02
#define REG_SCAN_LIMIT 0x03
#define REG_CONFIGURATION 0x04
#define REG_CONFIGURATION_S_BIT BIT(0)
#define REG_DIGIT(x) (0x20 + (x))
#define REG_DIGIT0 0x20
#define REG_DIGIT1 0x21
#define REG_DIGIT2 0x22
#define REG_DIGIT3 0x23
#define REG_SEGMENTS 0x24
#define REG_MAX REG_SEGMENTS
struct max6959_priv {
struct linedisp linedisp;
struct delayed_work work;
struct regmap *regmap;
};
static void max6959_disp_update(struct work_struct *work)
{
struct max6959_priv *priv = container_of(work, struct max6959_priv, work.work);
struct linedisp *linedisp = &priv->linedisp;
struct linedisp_map *map = linedisp->map;
char *s = linedisp->buf;
u8 buf[4];
/* Map segments according to datasheet */
buf[0] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1;
buf[1] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1;
buf[2] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1;
buf[3] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1;
regmap_bulk_write(priv->regmap, REG_DIGIT(0), buf, ARRAY_SIZE(buf));
}
static int max6959_linedisp_get_map_type(struct linedisp *linedisp)
{
struct max6959_priv *priv = container_of(linedisp, struct max6959_priv, linedisp);
INIT_DELAYED_WORK(&priv->work, max6959_disp_update);
return LINEDISP_MAP_SEG7;
}
static void max6959_linedisp_update(struct linedisp *linedisp)
{
struct max6959_priv *priv = container_of(linedisp, struct max6959_priv, linedisp);
schedule_delayed_work(&priv->work, 0);
}
static const struct linedisp_ops max6959_linedisp_ops = {
.get_map_type = max6959_linedisp_get_map_type,
.update = max6959_linedisp_update,
};
static int max6959_enable(struct max6959_priv *priv, bool enable)
{
return regmap_assign_bits(priv->regmap, REG_CONFIGURATION, REG_CONFIGURATION_S_BIT, enable);
}
static void max6959_power_off(void *priv)
{
max6959_enable(priv, false);
}
static int max6959_power_on(struct max6959_priv *priv)
{
struct device *dev = regmap_get_device(priv->regmap);
int ret;
ret = max6959_enable(priv, true);
if (ret)
return ret;
return devm_add_action_or_reset(dev, max6959_power_off, priv);
}
static const struct regmap_config max6959_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = REG_MAX,
.cache_type = REGCACHE_MAPLE,
};
static int max6959_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct max6959_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->regmap = devm_regmap_init_i2c(client, &max6959_regmap_config);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
ret = max6959_power_on(priv);
if (ret)
return ret;
ret = linedisp_register(&priv->linedisp, dev, 4, &max6959_linedisp_ops);
if (ret)
return ret;
i2c_set_clientdata(client, priv);
return 0;
}
static void max6959_i2c_remove(struct i2c_client *client)
{
struct max6959_priv *priv = i2c_get_clientdata(client);
cancel_delayed_work_sync(&priv->work);
linedisp_unregister(&priv->linedisp);
}
static int max6959_suspend(struct device *dev)
{
return max6959_enable(dev_get_drvdata(dev), false);
}
static int max6959_resume(struct device *dev)
{
return max6959_enable(dev_get_drvdata(dev), true);
}
static DEFINE_SIMPLE_DEV_PM_OPS(max6959_pm_ops, max6959_suspend, max6959_resume);
static const struct i2c_device_id max6959_i2c_id[] = {
{ "max6959" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max6959_i2c_id);
static const struct of_device_id max6959_of_table[] = {
{ .compatible = "maxim,max6959" },
{ }
};
MODULE_DEVICE_TABLE(of, max6959_of_table);
static struct i2c_driver max6959_i2c_driver = {
.driver = {
.name = "max6959",
.pm = pm_sleep_ptr(&max6959_pm_ops),
.of_match_table = max6959_of_table,
},
.probe = max6959_i2c_probe,
.remove = max6959_i2c_remove,
.id_table = max6959_i2c_id,
};
module_i2c_driver(max6959_i2c_driver);
MODULE_DESCRIPTION("MAX6958/6959 7-segment LED controller");
MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("LINEDISP");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for a 7-segment LED display
*
* The decimal point LED present on some devices is currently not
* supported.
*
* Copyright (C) Allied Telesis Labs
*/
#include <linux/bitmap.h>
#include <linux/container_of.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/map_to_7segment.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include "line-display.h"
struct seg_led_priv {
struct linedisp linedisp;
struct delayed_work work;
struct gpio_descs *segment_gpios;
};
static void seg_led_update(struct work_struct *work)
{
struct seg_led_priv *priv = container_of(work, struct seg_led_priv, work.work);
struct linedisp *linedisp = &priv->linedisp;
struct linedisp_map *map = linedisp->map;
DECLARE_BITMAP(values, 8) = { };
bitmap_set_value8(values, map_to_seg7(&map->map.seg7, linedisp->buf[0]), 0);
gpiod_multi_set_value_cansleep(priv->segment_gpios, values);
}
static int seg_led_linedisp_get_map_type(struct linedisp *linedisp)
{
struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp);
INIT_DELAYED_WORK(&priv->work, seg_led_update);
return LINEDISP_MAP_SEG7;
}
static void seg_led_linedisp_update(struct linedisp *linedisp)
{
struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp);
schedule_delayed_work(&priv->work, 0);
}
static const struct linedisp_ops seg_led_linedisp_ops = {
.get_map_type = seg_led_linedisp_get_map_type,
.update = seg_led_linedisp_update,
};
static int seg_led_probe(struct platform_device *pdev)
{
struct seg_led_priv *priv;
struct device *dev = &pdev->dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->segment_gpios = devm_gpiod_get_array(dev, "segment", GPIOD_OUT_LOW);
if (IS_ERR(priv->segment_gpios))
return PTR_ERR(priv->segment_gpios);
if (priv->segment_gpios->ndescs < 7 || priv->segment_gpios->ndescs > 8)
return -EINVAL;
return linedisp_register(&priv->linedisp, dev, 1, &seg_led_linedisp_ops);
}
static void seg_led_remove(struct platform_device *pdev)
{
struct seg_led_priv *priv = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&priv->work);
linedisp_unregister(&priv->linedisp);
}
static const struct of_device_id seg_led_of_match[] = {
{ .compatible = "gpio-7-segment"},
{}
};
MODULE_DEVICE_TABLE(of, seg_led_of_match);
static struct platform_driver seg_led_driver = {
.probe = seg_led_probe,
.remove = seg_led_remove,
.driver = {
.name = "seg-led-gpio",
.of_match_table = seg_led_of_match,
},
};
module_platform_driver(seg_led_driver);
MODULE_AUTHOR("Chris Packham <chris.packham@alliedtelesis.co.nz>");
MODULE_DESCRIPTION("7 segment LED driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("LINEDISP");