162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for the on-board character LCD found on some ARM reference boards 462306a36Sopenharmony_ci * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it 562306a36Sopenharmony_ci * https://en.wikipedia.org/wiki/HD44780_Character_LCD 662306a36Sopenharmony_ci * Currently it will just display the text "ARM Linux" and the linux version 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Author: Linus Walleij <triad@df.lth.se> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/interrupt.h> 1262306a36Sopenharmony_ci#include <linux/platform_device.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/completion.h> 1562306a36Sopenharmony_ci#include <linux/delay.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/workqueue.h> 1962306a36Sopenharmony_ci#include <generated/utsrelease.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define DRIVERNAME "arm-charlcd" 2262306a36Sopenharmony_ci#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/* Offsets to registers */ 2562306a36Sopenharmony_ci#define CHAR_COM 0x00U 2662306a36Sopenharmony_ci#define CHAR_DAT 0x04U 2762306a36Sopenharmony_ci#define CHAR_RD 0x08U 2862306a36Sopenharmony_ci#define CHAR_RAW 0x0CU 2962306a36Sopenharmony_ci#define CHAR_MASK 0x10U 3062306a36Sopenharmony_ci#define CHAR_STAT 0x14U 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define CHAR_RAW_CLEAR 0x00000000U 3362306a36Sopenharmony_ci#define CHAR_RAW_VALID 0x00000100U 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* Hitachi HD44780 display commands */ 3662306a36Sopenharmony_ci#define HD_CLEAR 0x01U 3762306a36Sopenharmony_ci#define HD_HOME 0x02U 3862306a36Sopenharmony_ci#define HD_ENTRYMODE 0x04U 3962306a36Sopenharmony_ci#define HD_ENTRYMODE_INCREMENT 0x02U 4062306a36Sopenharmony_ci#define HD_ENTRYMODE_SHIFT 0x01U 4162306a36Sopenharmony_ci#define HD_DISPCTRL 0x08U 4262306a36Sopenharmony_ci#define HD_DISPCTRL_ON 0x04U 4362306a36Sopenharmony_ci#define HD_DISPCTRL_CURSOR_ON 0x02U 4462306a36Sopenharmony_ci#define HD_DISPCTRL_CURSOR_BLINK 0x01U 4562306a36Sopenharmony_ci#define HD_CRSR_SHIFT 0x10U 4662306a36Sopenharmony_ci#define HD_CRSR_SHIFT_DISPLAY 0x08U 4762306a36Sopenharmony_ci#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U 4862306a36Sopenharmony_ci#define HD_FUNCSET 0x20U 4962306a36Sopenharmony_ci#define HD_FUNCSET_8BIT 0x10U 5062306a36Sopenharmony_ci#define HD_FUNCSET_2_LINES 0x08U 5162306a36Sopenharmony_ci#define HD_FUNCSET_FONT_5X10 0x04U 5262306a36Sopenharmony_ci#define HD_SET_CGRAM 0x40U 5362306a36Sopenharmony_ci#define HD_SET_DDRAM 0x80U 5462306a36Sopenharmony_ci#define HD_BUSY_FLAG 0x80U 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/** 5762306a36Sopenharmony_ci * struct charlcd - Private data structure 5862306a36Sopenharmony_ci * @dev: a pointer back to containing device 5962306a36Sopenharmony_ci * @phybase: the offset to the controller in physical memory 6062306a36Sopenharmony_ci * @physize: the size of the physical page 6162306a36Sopenharmony_ci * @virtbase: the offset to the controller in virtual memory 6262306a36Sopenharmony_ci * @irq: reserved interrupt number 6362306a36Sopenharmony_ci * @complete: completion structure for the last LCD command 6462306a36Sopenharmony_ci * @init_work: delayed work structure to initialize the display on boot 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_cistruct charlcd { 6762306a36Sopenharmony_ci struct device *dev; 6862306a36Sopenharmony_ci u32 phybase; 6962306a36Sopenharmony_ci u32 physize; 7062306a36Sopenharmony_ci void __iomem *virtbase; 7162306a36Sopenharmony_ci int irq; 7262306a36Sopenharmony_ci struct completion complete; 7362306a36Sopenharmony_ci struct delayed_work init_work; 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic irqreturn_t charlcd_interrupt(int irq, void *data) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct charlcd *lcd = data; 7962306a36Sopenharmony_ci u8 status; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci status = readl(lcd->virtbase + CHAR_STAT) & 0x01; 8262306a36Sopenharmony_ci /* Clear IRQ */ 8362306a36Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 8462306a36Sopenharmony_ci if (status) 8562306a36Sopenharmony_ci complete(&lcd->complete); 8662306a36Sopenharmony_ci else 8762306a36Sopenharmony_ci dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); 8862306a36Sopenharmony_ci return IRQ_HANDLED; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic void charlcd_wait_complete_irq(struct charlcd *lcd) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci int ret; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci ret = wait_for_completion_interruptible_timeout(&lcd->complete, 9762306a36Sopenharmony_ci CHARLCD_TIMEOUT); 9862306a36Sopenharmony_ci /* Disable IRQ after completion */ 9962306a36Sopenharmony_ci writel(0x00, lcd->virtbase + CHAR_MASK); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (ret < 0) { 10262306a36Sopenharmony_ci dev_err(lcd->dev, 10362306a36Sopenharmony_ci "wait_for_completion_interruptible_timeout() " 10462306a36Sopenharmony_ci "returned %d waiting for ready\n", ret); 10562306a36Sopenharmony_ci return; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (ret == 0) { 10962306a36Sopenharmony_ci dev_err(lcd->dev, "charlcd controller timed out " 11062306a36Sopenharmony_ci "waiting for ready\n"); 11162306a36Sopenharmony_ci return; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic u8 charlcd_4bit_read_char(struct charlcd *lcd) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci u8 data; 11862306a36Sopenharmony_ci u32 val; 11962306a36Sopenharmony_ci int i; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* If we can, use an IRQ to wait for the data, else poll */ 12262306a36Sopenharmony_ci if (lcd->irq >= 0) 12362306a36Sopenharmony_ci charlcd_wait_complete_irq(lcd); 12462306a36Sopenharmony_ci else { 12562306a36Sopenharmony_ci i = 0; 12662306a36Sopenharmony_ci val = 0; 12762306a36Sopenharmony_ci while (!(val & CHAR_RAW_VALID) && i < 10) { 12862306a36Sopenharmony_ci udelay(100); 12962306a36Sopenharmony_ci val = readl(lcd->virtbase + CHAR_RAW); 13062306a36Sopenharmony_ci i++; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci msleep(1); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* Read the 4 high bits of the data */ 13862306a36Sopenharmony_ci data = readl(lcd->virtbase + CHAR_RD) & 0xf0; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* 14162306a36Sopenharmony_ci * The second read for the low bits does not trigger an IRQ 14262306a36Sopenharmony_ci * so in this case we have to poll for the 4 lower bits 14362306a36Sopenharmony_ci */ 14462306a36Sopenharmony_ci i = 0; 14562306a36Sopenharmony_ci val = 0; 14662306a36Sopenharmony_ci while (!(val & CHAR_RAW_VALID) && i < 10) { 14762306a36Sopenharmony_ci udelay(100); 14862306a36Sopenharmony_ci val = readl(lcd->virtbase + CHAR_RAW); 14962306a36Sopenharmony_ci i++; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 15262306a36Sopenharmony_ci msleep(1); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* Read the 4 low bits of the data */ 15562306a36Sopenharmony_ci data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return data; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic bool charlcd_4bit_read_bf(struct charlcd *lcd) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci if (lcd->irq >= 0) { 16362306a36Sopenharmony_ci /* 16462306a36Sopenharmony_ci * If we'll use IRQs to wait for the busyflag, clear any 16562306a36Sopenharmony_ci * pending flag and enable IRQ 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 16862306a36Sopenharmony_ci init_completion(&lcd->complete); 16962306a36Sopenharmony_ci writel(0x01, lcd->virtbase + CHAR_MASK); 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci readl(lcd->virtbase + CHAR_COM); 17262306a36Sopenharmony_ci return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic void charlcd_4bit_wait_busy(struct charlcd *lcd) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci int retries = 50; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci udelay(100); 18062306a36Sopenharmony_ci while (charlcd_4bit_read_bf(lcd) && retries) 18162306a36Sopenharmony_ci retries--; 18262306a36Sopenharmony_ci if (!retries) 18362306a36Sopenharmony_ci dev_err(lcd->dev, "timeout waiting for busyflag\n"); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci u32 cmdlo = (cmd << 4) & 0xf0; 18962306a36Sopenharmony_ci u32 cmdhi = (cmd & 0xf0); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci writel(cmdhi, lcd->virtbase + CHAR_COM); 19262306a36Sopenharmony_ci udelay(10); 19362306a36Sopenharmony_ci writel(cmdlo, lcd->virtbase + CHAR_COM); 19462306a36Sopenharmony_ci charlcd_4bit_wait_busy(lcd); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic void charlcd_4bit_char(struct charlcd *lcd, u8 ch) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci u32 chlo = (ch << 4) & 0xf0; 20062306a36Sopenharmony_ci u32 chhi = (ch & 0xf0); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci writel(chhi, lcd->virtbase + CHAR_DAT); 20362306a36Sopenharmony_ci udelay(10); 20462306a36Sopenharmony_ci writel(chlo, lcd->virtbase + CHAR_DAT); 20562306a36Sopenharmony_ci charlcd_4bit_wait_busy(lcd); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci u8 offset; 21162306a36Sopenharmony_ci int i; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* 21462306a36Sopenharmony_ci * We support line 0, 1 21562306a36Sopenharmony_ci * Line 1 runs from 0x00..0x27 21662306a36Sopenharmony_ci * Line 2 runs from 0x28..0x4f 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci if (line == 0) 21962306a36Sopenharmony_ci offset = 0; 22062306a36Sopenharmony_ci else if (line == 1) 22162306a36Sopenharmony_ci offset = 0x28; 22262306a36Sopenharmony_ci else 22362306a36Sopenharmony_ci return; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* Set offset */ 22662306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci /* Send string */ 22962306a36Sopenharmony_ci for (i = 0; i < strlen(str) && i < 0x28; i++) 23062306a36Sopenharmony_ci charlcd_4bit_char(lcd, str[i]); 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic void charlcd_4bit_init(struct charlcd *lcd) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci /* These commands cannot be checked with the busy flag */ 23662306a36Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 23762306a36Sopenharmony_ci msleep(5); 23862306a36Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 23962306a36Sopenharmony_ci udelay(100); 24062306a36Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 24162306a36Sopenharmony_ci udelay(100); 24262306a36Sopenharmony_ci /* Go to 4bit mode */ 24362306a36Sopenharmony_ci writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); 24462306a36Sopenharmony_ci udelay(100); 24562306a36Sopenharmony_ci /* 24662306a36Sopenharmony_ci * 4bit mode, 2 lines, 5x8 font, after this the number of lines 24762306a36Sopenharmony_ci * and the font cannot be changed until the next initialization sequence 24862306a36Sopenharmony_ci */ 24962306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); 25062306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); 25162306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); 25262306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_CLEAR); 25362306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_HOME); 25462306a36Sopenharmony_ci /* Put something useful in the display */ 25562306a36Sopenharmony_ci charlcd_4bit_print(lcd, 0, "ARM Linux"); 25662306a36Sopenharmony_ci charlcd_4bit_print(lcd, 1, UTS_RELEASE); 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic void charlcd_init_work(struct work_struct *work) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci struct charlcd *lcd = 26262306a36Sopenharmony_ci container_of(work, struct charlcd, init_work.work); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci charlcd_4bit_init(lcd); 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic int __init charlcd_probe(struct platform_device *pdev) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci int ret; 27062306a36Sopenharmony_ci struct charlcd *lcd; 27162306a36Sopenharmony_ci struct resource *res; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); 27462306a36Sopenharmony_ci if (!lcd) 27562306a36Sopenharmony_ci return -ENOMEM; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci lcd->dev = &pdev->dev; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 28062306a36Sopenharmony_ci if (!res) { 28162306a36Sopenharmony_ci ret = -ENOENT; 28262306a36Sopenharmony_ci goto out_no_resource; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci lcd->phybase = res->start; 28562306a36Sopenharmony_ci lcd->physize = resource_size(res); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci if (request_mem_region(lcd->phybase, lcd->physize, 28862306a36Sopenharmony_ci DRIVERNAME) == NULL) { 28962306a36Sopenharmony_ci ret = -EBUSY; 29062306a36Sopenharmony_ci goto out_no_memregion; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci lcd->virtbase = ioremap(lcd->phybase, lcd->physize); 29462306a36Sopenharmony_ci if (!lcd->virtbase) { 29562306a36Sopenharmony_ci ret = -ENOMEM; 29662306a36Sopenharmony_ci goto out_no_memregion; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci lcd->irq = platform_get_irq(pdev, 0); 30062306a36Sopenharmony_ci /* If no IRQ is supplied, we'll survive without it */ 30162306a36Sopenharmony_ci if (lcd->irq >= 0) { 30262306a36Sopenharmony_ci if (request_irq(lcd->irq, charlcd_interrupt, 0, 30362306a36Sopenharmony_ci DRIVERNAME, lcd)) { 30462306a36Sopenharmony_ci ret = -EIO; 30562306a36Sopenharmony_ci goto out_no_irq; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci platform_set_drvdata(pdev, lcd); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci /* 31262306a36Sopenharmony_ci * Initialize the display in a delayed work, because 31362306a36Sopenharmony_ci * it is VERY slow and would slow down the boot of the system. 31462306a36Sopenharmony_ci */ 31562306a36Sopenharmony_ci INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); 31662306a36Sopenharmony_ci schedule_delayed_work(&lcd->init_work, 0); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", 31962306a36Sopenharmony_ci lcd->phybase); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci return 0; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ciout_no_irq: 32462306a36Sopenharmony_ci iounmap(lcd->virtbase); 32562306a36Sopenharmony_ciout_no_memregion: 32662306a36Sopenharmony_ci release_mem_region(lcd->phybase, SZ_4K); 32762306a36Sopenharmony_ciout_no_resource: 32862306a36Sopenharmony_ci kfree(lcd); 32962306a36Sopenharmony_ci return ret; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic int charlcd_suspend(struct device *dev) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci struct charlcd *lcd = dev_get_drvdata(dev); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* Power the display off */ 33762306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL); 33862306a36Sopenharmony_ci return 0; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic int charlcd_resume(struct device *dev) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct charlcd *lcd = dev_get_drvdata(dev); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci /* Turn the display back on */ 34662306a36Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); 34762306a36Sopenharmony_ci return 0; 34862306a36Sopenharmony_ci} 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic const struct dev_pm_ops charlcd_pm_ops = { 35162306a36Sopenharmony_ci .suspend = charlcd_suspend, 35262306a36Sopenharmony_ci .resume = charlcd_resume, 35362306a36Sopenharmony_ci}; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cistatic const struct of_device_id charlcd_match[] = { 35662306a36Sopenharmony_ci { .compatible = "arm,versatile-lcd", }, 35762306a36Sopenharmony_ci {} 35862306a36Sopenharmony_ci}; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_cistatic struct platform_driver charlcd_driver = { 36162306a36Sopenharmony_ci .driver = { 36262306a36Sopenharmony_ci .name = DRIVERNAME, 36362306a36Sopenharmony_ci .pm = &charlcd_pm_ops, 36462306a36Sopenharmony_ci .suppress_bind_attrs = true, 36562306a36Sopenharmony_ci .of_match_table = of_match_ptr(charlcd_match), 36662306a36Sopenharmony_ci }, 36762306a36Sopenharmony_ci}; 36862306a36Sopenharmony_cibuiltin_platform_driver_probe(charlcd_driver, charlcd_probe); 369