18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for the on-board character LCD found on some ARM reference boards 48c2ecf20Sopenharmony_ci * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it 58c2ecf20Sopenharmony_ci * https://en.wikipedia.org/wiki/HD44780_Character_LCD 68c2ecf20Sopenharmony_ci * Currently it will just display the text "ARM Linux" and the linux version 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Author: Linus Walleij <triad@df.lth.se> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/completion.h> 158c2ecf20Sopenharmony_ci#include <linux/delay.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 198c2ecf20Sopenharmony_ci#include <generated/utsrelease.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define DRIVERNAME "arm-charlcd" 228c2ecf20Sopenharmony_ci#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Offsets to registers */ 258c2ecf20Sopenharmony_ci#define CHAR_COM 0x00U 268c2ecf20Sopenharmony_ci#define CHAR_DAT 0x04U 278c2ecf20Sopenharmony_ci#define CHAR_RD 0x08U 288c2ecf20Sopenharmony_ci#define CHAR_RAW 0x0CU 298c2ecf20Sopenharmony_ci#define CHAR_MASK 0x10U 308c2ecf20Sopenharmony_ci#define CHAR_STAT 0x14U 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define CHAR_RAW_CLEAR 0x00000000U 338c2ecf20Sopenharmony_ci#define CHAR_RAW_VALID 0x00000100U 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* Hitachi HD44780 display commands */ 368c2ecf20Sopenharmony_ci#define HD_CLEAR 0x01U 378c2ecf20Sopenharmony_ci#define HD_HOME 0x02U 388c2ecf20Sopenharmony_ci#define HD_ENTRYMODE 0x04U 398c2ecf20Sopenharmony_ci#define HD_ENTRYMODE_INCREMENT 0x02U 408c2ecf20Sopenharmony_ci#define HD_ENTRYMODE_SHIFT 0x01U 418c2ecf20Sopenharmony_ci#define HD_DISPCTRL 0x08U 428c2ecf20Sopenharmony_ci#define HD_DISPCTRL_ON 0x04U 438c2ecf20Sopenharmony_ci#define HD_DISPCTRL_CURSOR_ON 0x02U 448c2ecf20Sopenharmony_ci#define HD_DISPCTRL_CURSOR_BLINK 0x01U 458c2ecf20Sopenharmony_ci#define HD_CRSR_SHIFT 0x10U 468c2ecf20Sopenharmony_ci#define HD_CRSR_SHIFT_DISPLAY 0x08U 478c2ecf20Sopenharmony_ci#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U 488c2ecf20Sopenharmony_ci#define HD_FUNCSET 0x20U 498c2ecf20Sopenharmony_ci#define HD_FUNCSET_8BIT 0x10U 508c2ecf20Sopenharmony_ci#define HD_FUNCSET_2_LINES 0x08U 518c2ecf20Sopenharmony_ci#define HD_FUNCSET_FONT_5X10 0x04U 528c2ecf20Sopenharmony_ci#define HD_SET_CGRAM 0x40U 538c2ecf20Sopenharmony_ci#define HD_SET_DDRAM 0x80U 548c2ecf20Sopenharmony_ci#define HD_BUSY_FLAG 0x80U 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/** 578c2ecf20Sopenharmony_ci * struct charlcd - Private data structure 588c2ecf20Sopenharmony_ci * @dev: a pointer back to containing device 598c2ecf20Sopenharmony_ci * @phybase: the offset to the controller in physical memory 608c2ecf20Sopenharmony_ci * @physize: the size of the physical page 618c2ecf20Sopenharmony_ci * @virtbase: the offset to the controller in virtual memory 628c2ecf20Sopenharmony_ci * @irq: reserved interrupt number 638c2ecf20Sopenharmony_ci * @complete: completion structure for the last LCD command 648c2ecf20Sopenharmony_ci * @init_work: delayed work structure to initialize the display on boot 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_cistruct charlcd { 678c2ecf20Sopenharmony_ci struct device *dev; 688c2ecf20Sopenharmony_ci u32 phybase; 698c2ecf20Sopenharmony_ci u32 physize; 708c2ecf20Sopenharmony_ci void __iomem *virtbase; 718c2ecf20Sopenharmony_ci int irq; 728c2ecf20Sopenharmony_ci struct completion complete; 738c2ecf20Sopenharmony_ci struct delayed_work init_work; 748c2ecf20Sopenharmony_ci}; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic irqreturn_t charlcd_interrupt(int irq, void *data) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct charlcd *lcd = data; 798c2ecf20Sopenharmony_ci u8 status; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci status = readl(lcd->virtbase + CHAR_STAT) & 0x01; 828c2ecf20Sopenharmony_ci /* Clear IRQ */ 838c2ecf20Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 848c2ecf20Sopenharmony_ci if (status) 858c2ecf20Sopenharmony_ci complete(&lcd->complete); 868c2ecf20Sopenharmony_ci else 878c2ecf20Sopenharmony_ci dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); 888c2ecf20Sopenharmony_ci return IRQ_HANDLED; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic void charlcd_wait_complete_irq(struct charlcd *lcd) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci int ret; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci ret = wait_for_completion_interruptible_timeout(&lcd->complete, 978c2ecf20Sopenharmony_ci CHARLCD_TIMEOUT); 988c2ecf20Sopenharmony_ci /* Disable IRQ after completion */ 998c2ecf20Sopenharmony_ci writel(0x00, lcd->virtbase + CHAR_MASK); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci if (ret < 0) { 1028c2ecf20Sopenharmony_ci dev_err(lcd->dev, 1038c2ecf20Sopenharmony_ci "wait_for_completion_interruptible_timeout() " 1048c2ecf20Sopenharmony_ci "returned %d waiting for ready\n", ret); 1058c2ecf20Sopenharmony_ci return; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (ret == 0) { 1098c2ecf20Sopenharmony_ci dev_err(lcd->dev, "charlcd controller timed out " 1108c2ecf20Sopenharmony_ci "waiting for ready\n"); 1118c2ecf20Sopenharmony_ci return; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic u8 charlcd_4bit_read_char(struct charlcd *lcd) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci u8 data; 1188c2ecf20Sopenharmony_ci u32 val; 1198c2ecf20Sopenharmony_ci int i; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci /* If we can, use an IRQ to wait for the data, else poll */ 1228c2ecf20Sopenharmony_ci if (lcd->irq >= 0) 1238c2ecf20Sopenharmony_ci charlcd_wait_complete_irq(lcd); 1248c2ecf20Sopenharmony_ci else { 1258c2ecf20Sopenharmony_ci i = 0; 1268c2ecf20Sopenharmony_ci val = 0; 1278c2ecf20Sopenharmony_ci while (!(val & CHAR_RAW_VALID) && i < 10) { 1288c2ecf20Sopenharmony_ci udelay(100); 1298c2ecf20Sopenharmony_ci val = readl(lcd->virtbase + CHAR_RAW); 1308c2ecf20Sopenharmony_ci i++; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci msleep(1); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* Read the 4 high bits of the data */ 1388c2ecf20Sopenharmony_ci data = readl(lcd->virtbase + CHAR_RD) & 0xf0; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci /* 1418c2ecf20Sopenharmony_ci * The second read for the low bits does not trigger an IRQ 1428c2ecf20Sopenharmony_ci * so in this case we have to poll for the 4 lower bits 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci i = 0; 1458c2ecf20Sopenharmony_ci val = 0; 1468c2ecf20Sopenharmony_ci while (!(val & CHAR_RAW_VALID) && i < 10) { 1478c2ecf20Sopenharmony_ci udelay(100); 1488c2ecf20Sopenharmony_ci val = readl(lcd->virtbase + CHAR_RAW); 1498c2ecf20Sopenharmony_ci i++; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 1528c2ecf20Sopenharmony_ci msleep(1); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* Read the 4 low bits of the data */ 1558c2ecf20Sopenharmony_ci data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci return data; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic bool charlcd_4bit_read_bf(struct charlcd *lcd) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci if (lcd->irq >= 0) { 1638c2ecf20Sopenharmony_ci /* 1648c2ecf20Sopenharmony_ci * If we'll use IRQs to wait for the busyflag, clear any 1658c2ecf20Sopenharmony_ci * pending flag and enable IRQ 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_ci writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); 1688c2ecf20Sopenharmony_ci init_completion(&lcd->complete); 1698c2ecf20Sopenharmony_ci writel(0x01, lcd->virtbase + CHAR_MASK); 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci readl(lcd->virtbase + CHAR_COM); 1728c2ecf20Sopenharmony_ci return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic void charlcd_4bit_wait_busy(struct charlcd *lcd) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci int retries = 50; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci udelay(100); 1808c2ecf20Sopenharmony_ci while (charlcd_4bit_read_bf(lcd) && retries) 1818c2ecf20Sopenharmony_ci retries--; 1828c2ecf20Sopenharmony_ci if (!retries) 1838c2ecf20Sopenharmony_ci dev_err(lcd->dev, "timeout waiting for busyflag\n"); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci u32 cmdlo = (cmd << 4) & 0xf0; 1898c2ecf20Sopenharmony_ci u32 cmdhi = (cmd & 0xf0); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci writel(cmdhi, lcd->virtbase + CHAR_COM); 1928c2ecf20Sopenharmony_ci udelay(10); 1938c2ecf20Sopenharmony_ci writel(cmdlo, lcd->virtbase + CHAR_COM); 1948c2ecf20Sopenharmony_ci charlcd_4bit_wait_busy(lcd); 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic void charlcd_4bit_char(struct charlcd *lcd, u8 ch) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci u32 chlo = (ch << 4) & 0xf0; 2008c2ecf20Sopenharmony_ci u32 chhi = (ch & 0xf0); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci writel(chhi, lcd->virtbase + CHAR_DAT); 2038c2ecf20Sopenharmony_ci udelay(10); 2048c2ecf20Sopenharmony_ci writel(chlo, lcd->virtbase + CHAR_DAT); 2058c2ecf20Sopenharmony_ci charlcd_4bit_wait_busy(lcd); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci u8 offset; 2118c2ecf20Sopenharmony_ci int i; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* 2148c2ecf20Sopenharmony_ci * We support line 0, 1 2158c2ecf20Sopenharmony_ci * Line 1 runs from 0x00..0x27 2168c2ecf20Sopenharmony_ci * Line 2 runs from 0x28..0x4f 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_ci if (line == 0) 2198c2ecf20Sopenharmony_ci offset = 0; 2208c2ecf20Sopenharmony_ci else if (line == 1) 2218c2ecf20Sopenharmony_ci offset = 0x28; 2228c2ecf20Sopenharmony_ci else 2238c2ecf20Sopenharmony_ci return; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* Set offset */ 2268c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* Send string */ 2298c2ecf20Sopenharmony_ci for (i = 0; i < strlen(str) && i < 0x28; i++) 2308c2ecf20Sopenharmony_ci charlcd_4bit_char(lcd, str[i]); 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic void charlcd_4bit_init(struct charlcd *lcd) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci /* These commands cannot be checked with the busy flag */ 2368c2ecf20Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 2378c2ecf20Sopenharmony_ci msleep(5); 2388c2ecf20Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 2398c2ecf20Sopenharmony_ci udelay(100); 2408c2ecf20Sopenharmony_ci writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); 2418c2ecf20Sopenharmony_ci udelay(100); 2428c2ecf20Sopenharmony_ci /* Go to 4bit mode */ 2438c2ecf20Sopenharmony_ci writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); 2448c2ecf20Sopenharmony_ci udelay(100); 2458c2ecf20Sopenharmony_ci /* 2468c2ecf20Sopenharmony_ci * 4bit mode, 2 lines, 5x8 font, after this the number of lines 2478c2ecf20Sopenharmony_ci * and the font cannot be changed until the next initialization sequence 2488c2ecf20Sopenharmony_ci */ 2498c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); 2508c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); 2518c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); 2528c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_CLEAR); 2538c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_HOME); 2548c2ecf20Sopenharmony_ci /* Put something useful in the display */ 2558c2ecf20Sopenharmony_ci charlcd_4bit_print(lcd, 0, "ARM Linux"); 2568c2ecf20Sopenharmony_ci charlcd_4bit_print(lcd, 1, UTS_RELEASE); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic void charlcd_init_work(struct work_struct *work) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct charlcd *lcd = 2628c2ecf20Sopenharmony_ci container_of(work, struct charlcd, init_work.work); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci charlcd_4bit_init(lcd); 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic int __init charlcd_probe(struct platform_device *pdev) 2688c2ecf20Sopenharmony_ci{ 2698c2ecf20Sopenharmony_ci int ret; 2708c2ecf20Sopenharmony_ci struct charlcd *lcd; 2718c2ecf20Sopenharmony_ci struct resource *res; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); 2748c2ecf20Sopenharmony_ci if (!lcd) 2758c2ecf20Sopenharmony_ci return -ENOMEM; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci lcd->dev = &pdev->dev; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2808c2ecf20Sopenharmony_ci if (!res) { 2818c2ecf20Sopenharmony_ci ret = -ENOENT; 2828c2ecf20Sopenharmony_ci goto out_no_resource; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci lcd->phybase = res->start; 2858c2ecf20Sopenharmony_ci lcd->physize = resource_size(res); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (request_mem_region(lcd->phybase, lcd->physize, 2888c2ecf20Sopenharmony_ci DRIVERNAME) == NULL) { 2898c2ecf20Sopenharmony_ci ret = -EBUSY; 2908c2ecf20Sopenharmony_ci goto out_no_memregion; 2918c2ecf20Sopenharmony_ci } 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci lcd->virtbase = ioremap(lcd->phybase, lcd->physize); 2948c2ecf20Sopenharmony_ci if (!lcd->virtbase) { 2958c2ecf20Sopenharmony_ci ret = -ENOMEM; 2968c2ecf20Sopenharmony_ci goto out_no_memregion; 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci lcd->irq = platform_get_irq(pdev, 0); 3008c2ecf20Sopenharmony_ci /* If no IRQ is supplied, we'll survive without it */ 3018c2ecf20Sopenharmony_ci if (lcd->irq >= 0) { 3028c2ecf20Sopenharmony_ci if (request_irq(lcd->irq, charlcd_interrupt, 0, 3038c2ecf20Sopenharmony_ci DRIVERNAME, lcd)) { 3048c2ecf20Sopenharmony_ci ret = -EIO; 3058c2ecf20Sopenharmony_ci goto out_no_irq; 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, lcd); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci /* 3128c2ecf20Sopenharmony_ci * Initialize the display in a delayed work, because 3138c2ecf20Sopenharmony_ci * it is VERY slow and would slow down the boot of the system. 3148c2ecf20Sopenharmony_ci */ 3158c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); 3168c2ecf20Sopenharmony_ci schedule_delayed_work(&lcd->init_work, 0); 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", 3198c2ecf20Sopenharmony_ci lcd->phybase); 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci return 0; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ciout_no_irq: 3248c2ecf20Sopenharmony_ci iounmap(lcd->virtbase); 3258c2ecf20Sopenharmony_ciout_no_memregion: 3268c2ecf20Sopenharmony_ci release_mem_region(lcd->phybase, SZ_4K); 3278c2ecf20Sopenharmony_ciout_no_resource: 3288c2ecf20Sopenharmony_ci kfree(lcd); 3298c2ecf20Sopenharmony_ci return ret; 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_cistatic int charlcd_suspend(struct device *dev) 3338c2ecf20Sopenharmony_ci{ 3348c2ecf20Sopenharmony_ci struct charlcd *lcd = dev_get_drvdata(dev); 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci /* Power the display off */ 3378c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL); 3388c2ecf20Sopenharmony_ci return 0; 3398c2ecf20Sopenharmony_ci} 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cistatic int charlcd_resume(struct device *dev) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci struct charlcd *lcd = dev_get_drvdata(dev); 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci /* Turn the display back on */ 3468c2ecf20Sopenharmony_ci charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); 3478c2ecf20Sopenharmony_ci return 0; 3488c2ecf20Sopenharmony_ci} 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_cistatic const struct dev_pm_ops charlcd_pm_ops = { 3518c2ecf20Sopenharmony_ci .suspend = charlcd_suspend, 3528c2ecf20Sopenharmony_ci .resume = charlcd_resume, 3538c2ecf20Sopenharmony_ci}; 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_cistatic const struct of_device_id charlcd_match[] = { 3568c2ecf20Sopenharmony_ci { .compatible = "arm,versatile-lcd", }, 3578c2ecf20Sopenharmony_ci {} 3588c2ecf20Sopenharmony_ci}; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_cistatic struct platform_driver charlcd_driver = { 3618c2ecf20Sopenharmony_ci .driver = { 3628c2ecf20Sopenharmony_ci .name = DRIVERNAME, 3638c2ecf20Sopenharmony_ci .pm = &charlcd_pm_ops, 3648c2ecf20Sopenharmony_ci .suppress_bind_attrs = true, 3658c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(charlcd_match), 3668c2ecf20Sopenharmony_ci }, 3678c2ecf20Sopenharmony_ci}; 3688c2ecf20Sopenharmony_cibuiltin_platform_driver_probe(charlcd_driver, charlcd_probe); 369