10cad855fSPaul Burton /* 20cad855fSPaul Burton * Copyright (C) 2016 Imagination Technologies 3fb615d61SPaul Burton * Author: Paul Burton <paul.burton@mips.com> 40cad855fSPaul Burton * 50cad855fSPaul Burton * This program is free software; you can redistribute it and/or modify it 60cad855fSPaul Burton * under the terms of the GNU General Public License as published by the 70cad855fSPaul Burton * Free Software Foundation; either version 2 of the License, or (at your 80cad855fSPaul Burton * option) any later version. 90cad855fSPaul Burton */ 100cad855fSPaul Burton 110cad855fSPaul Burton #include <generated/utsrelease.h> 120cad855fSPaul Burton #include <linux/kernel.h> 130cad855fSPaul Burton #include <linux/io.h> 140cad855fSPaul Burton #include <linux/mfd/syscon.h> 150cad855fSPaul Burton #include <linux/module.h> 160cad855fSPaul Burton #include <linux/of_address.h> 170cad855fSPaul Burton #include <linux/of_platform.h> 180cad855fSPaul Burton #include <linux/platform_device.h> 190cad855fSPaul Burton #include <linux/regmap.h> 200cad855fSPaul Burton #include <linux/slab.h> 210cad855fSPaul Burton #include <linux/sysfs.h> 220cad855fSPaul Burton 230cad855fSPaul Burton struct img_ascii_lcd_ctx; 240cad855fSPaul Burton 250cad855fSPaul Burton /** 260cad855fSPaul Burton * struct img_ascii_lcd_config - Configuration information about an LCD model 270cad855fSPaul Burton * @num_chars: the number of characters the LCD can display 280cad855fSPaul Burton * @external_regmap: true if registers are in a system controller, else false 290cad855fSPaul Burton * @update: function called to update the LCD 300cad855fSPaul Burton */ 310cad855fSPaul Burton struct img_ascii_lcd_config { 320cad855fSPaul Burton unsigned int num_chars; 330cad855fSPaul Burton bool external_regmap; 340cad855fSPaul Burton void (*update)(struct img_ascii_lcd_ctx *ctx); 350cad855fSPaul Burton }; 360cad855fSPaul Burton 370cad855fSPaul Burton /** 380cad855fSPaul Burton * struct img_ascii_lcd_ctx - Private data structure 390cad855fSPaul Burton * @pdev: the ASCII LCD platform device 400cad855fSPaul Burton * @base: the base address of the LCD registers 410cad855fSPaul Burton * @regmap: the regmap through which LCD registers are accessed 420cad855fSPaul Burton * @offset: the offset within regmap to the start of the LCD registers 430cad855fSPaul Burton * @cfg: pointer to the LCD model configuration 440cad855fSPaul Burton * @message: the full message to display or scroll on the LCD 450cad855fSPaul Burton * @message_len: the length of the @message string 460cad855fSPaul Burton * @scroll_pos: index of the first character of @message currently displayed 470cad855fSPaul Burton * @scroll_rate: scroll interval in jiffies 480cad855fSPaul Burton * @timer: timer used to implement scrolling 490cad855fSPaul Burton * @curr: the string currently displayed on the LCD 500cad855fSPaul Burton */ 510cad855fSPaul Burton struct img_ascii_lcd_ctx { 520cad855fSPaul Burton struct platform_device *pdev; 530cad855fSPaul Burton union { 540cad855fSPaul Burton void __iomem *base; 550cad855fSPaul Burton struct regmap *regmap; 560cad855fSPaul Burton }; 570cad855fSPaul Burton u32 offset; 580cad855fSPaul Burton const struct img_ascii_lcd_config *cfg; 590cad855fSPaul Burton char *message; 600cad855fSPaul Burton unsigned int message_len; 610cad855fSPaul Burton unsigned int scroll_pos; 620cad855fSPaul Burton unsigned int scroll_rate; 630cad855fSPaul Burton struct timer_list timer; 640cad855fSPaul Burton char curr[] __aligned(8); 650cad855fSPaul Burton }; 660cad855fSPaul Burton 670cad855fSPaul Burton /* 680cad855fSPaul Burton * MIPS Boston development board 690cad855fSPaul Burton */ 700cad855fSPaul Burton 710cad855fSPaul Burton static void boston_update(struct img_ascii_lcd_ctx *ctx) 720cad855fSPaul Burton { 730cad855fSPaul Burton ulong val; 740cad855fSPaul Burton 750cad855fSPaul Burton #if BITS_PER_LONG == 64 760cad855fSPaul Burton val = *((u64 *)&ctx->curr[0]); 770cad855fSPaul Burton __raw_writeq(val, ctx->base); 780cad855fSPaul Burton #elif BITS_PER_LONG == 32 790cad855fSPaul Burton val = *((u32 *)&ctx->curr[0]); 800cad855fSPaul Burton __raw_writel(val, ctx->base); 810cad855fSPaul Burton val = *((u32 *)&ctx->curr[4]); 820cad855fSPaul Burton __raw_writel(val, ctx->base + 4); 830cad855fSPaul Burton #else 840cad855fSPaul Burton # error Not 32 or 64 bit 850cad855fSPaul Burton #endif 860cad855fSPaul Burton } 870cad855fSPaul Burton 880cad855fSPaul Burton static struct img_ascii_lcd_config boston_config = { 890cad855fSPaul Burton .num_chars = 8, 900cad855fSPaul Burton .update = boston_update, 910cad855fSPaul Burton }; 920cad855fSPaul Burton 930cad855fSPaul Burton /* 940cad855fSPaul Burton * MIPS Malta development board 950cad855fSPaul Burton */ 960cad855fSPaul Burton 970cad855fSPaul Burton static void malta_update(struct img_ascii_lcd_ctx *ctx) 980cad855fSPaul Burton { 990cad855fSPaul Burton unsigned int i; 1000cad855fSPaul Burton int err; 1010cad855fSPaul Burton 1020cad855fSPaul Burton for (i = 0; i < ctx->cfg->num_chars; i++) { 1030cad855fSPaul Burton err = regmap_write(ctx->regmap, 1040cad855fSPaul Burton ctx->offset + (i * 8), ctx->curr[i]); 1050cad855fSPaul Burton if (err) 1060cad855fSPaul Burton break; 1070cad855fSPaul Burton } 1080cad855fSPaul Burton 1090cad855fSPaul Burton if (unlikely(err)) 1100cad855fSPaul Burton pr_err_ratelimited("Failed to update LCD display: %d\n", err); 1110cad855fSPaul Burton } 1120cad855fSPaul Burton 1130cad855fSPaul Burton static struct img_ascii_lcd_config malta_config = { 1140cad855fSPaul Burton .num_chars = 8, 1150cad855fSPaul Burton .external_regmap = true, 1160cad855fSPaul Burton .update = malta_update, 1170cad855fSPaul Burton }; 1180cad855fSPaul Burton 1190cad855fSPaul Burton /* 1200cad855fSPaul Burton * MIPS SEAD3 development board 1210cad855fSPaul Burton */ 1220cad855fSPaul Burton 1230cad855fSPaul Burton enum { 1240cad855fSPaul Burton SEAD3_REG_LCD_CTRL = 0x00, 1250cad855fSPaul Burton #define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7) 1260cad855fSPaul Burton SEAD3_REG_LCD_DATA = 0x08, 1270cad855fSPaul Burton SEAD3_REG_CPLD_STATUS = 0x10, 1280cad855fSPaul Burton #define SEAD3_REG_CPLD_STATUS_BUSY BIT(0) 1290cad855fSPaul Burton SEAD3_REG_CPLD_DATA = 0x18, 1300cad855fSPaul Burton #define SEAD3_REG_CPLD_DATA_BUSY BIT(7) 1310cad855fSPaul Burton }; 1320cad855fSPaul Burton 1330cad855fSPaul Burton static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx) 1340cad855fSPaul Burton { 1350cad855fSPaul Burton unsigned int status; 1360cad855fSPaul Burton int err; 1370cad855fSPaul Burton 1380cad855fSPaul Burton do { 1390cad855fSPaul Burton err = regmap_read(ctx->regmap, 1400cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_STATUS, 1410cad855fSPaul Burton &status); 1420cad855fSPaul Burton if (err) 1430cad855fSPaul Burton return err; 1440cad855fSPaul Burton } while (status & SEAD3_REG_CPLD_STATUS_BUSY); 1450cad855fSPaul Burton 1460cad855fSPaul Burton return 0; 1470cad855fSPaul Burton 1480cad855fSPaul Burton } 1490cad855fSPaul Burton 1500cad855fSPaul Burton static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx) 1510cad855fSPaul Burton { 1520cad855fSPaul Burton unsigned int cpld_data; 1530cad855fSPaul Burton int err; 1540cad855fSPaul Burton 1550cad855fSPaul Burton err = sead3_wait_sm_idle(ctx); 1560cad855fSPaul Burton if (err) 1570cad855fSPaul Burton return err; 1580cad855fSPaul Burton 1590cad855fSPaul Burton do { 1600cad855fSPaul Burton err = regmap_read(ctx->regmap, 1610cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL, 1620cad855fSPaul Burton &cpld_data); 1630cad855fSPaul Burton if (err) 1640cad855fSPaul Burton return err; 1650cad855fSPaul Burton 1660cad855fSPaul Burton err = sead3_wait_sm_idle(ctx); 1670cad855fSPaul Burton if (err) 1680cad855fSPaul Burton return err; 1690cad855fSPaul Burton 1700cad855fSPaul Burton err = regmap_read(ctx->regmap, 1710cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_DATA, 1720cad855fSPaul Burton &cpld_data); 1730cad855fSPaul Burton if (err) 1740cad855fSPaul Burton return err; 1750cad855fSPaul Burton } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY); 1760cad855fSPaul Burton 1770cad855fSPaul Burton return 0; 1780cad855fSPaul Burton } 1790cad855fSPaul Burton 1800cad855fSPaul Burton static void sead3_update(struct img_ascii_lcd_ctx *ctx) 1810cad855fSPaul Burton { 1820cad855fSPaul Burton unsigned int i; 1830cad855fSPaul Burton int err; 1840cad855fSPaul Burton 1850cad855fSPaul Burton for (i = 0; i < ctx->cfg->num_chars; i++) { 1860cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx); 1870cad855fSPaul Burton if (err) 1880cad855fSPaul Burton break; 1890cad855fSPaul Burton 1900cad855fSPaul Burton err = regmap_write(ctx->regmap, 1910cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL, 1920cad855fSPaul Burton SEAD3_REG_LCD_CTRL_SETDRAM | i); 1930cad855fSPaul Burton if (err) 1940cad855fSPaul Burton break; 1950cad855fSPaul Burton 1960cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx); 1970cad855fSPaul Burton if (err) 1980cad855fSPaul Burton break; 1990cad855fSPaul Burton 2000cad855fSPaul Burton err = regmap_write(ctx->regmap, 2010cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_DATA, 2020cad855fSPaul Burton ctx->curr[i]); 2030cad855fSPaul Burton if (err) 2040cad855fSPaul Burton break; 2050cad855fSPaul Burton } 2060cad855fSPaul Burton 2070cad855fSPaul Burton if (unlikely(err)) 2080cad855fSPaul Burton pr_err_ratelimited("Failed to update LCD display: %d\n", err); 2090cad855fSPaul Burton } 2100cad855fSPaul Burton 2110cad855fSPaul Burton static struct img_ascii_lcd_config sead3_config = { 2120cad855fSPaul Burton .num_chars = 16, 2130cad855fSPaul Burton .external_regmap = true, 2140cad855fSPaul Burton .update = sead3_update, 2150cad855fSPaul Burton }; 2160cad855fSPaul Burton 2170cad855fSPaul Burton static const struct of_device_id img_ascii_lcd_matches[] = { 2180cad855fSPaul Burton { .compatible = "img,boston-lcd", .data = &boston_config }, 2190cad855fSPaul Burton { .compatible = "mti,malta-lcd", .data = &malta_config }, 2200cad855fSPaul Burton { .compatible = "mti,sead3-lcd", .data = &sead3_config }, 221abda288bSDmitry Torokhov { /* sentinel */ } 2220cad855fSPaul Burton }; 223750100a4SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); 2240cad855fSPaul Burton 2250cad855fSPaul Burton /** 2260cad855fSPaul Burton * img_ascii_lcd_scroll() - scroll the display by a character 2276a78b4ddSMiguel Ojeda * @t: really a pointer to the private data structure 2280cad855fSPaul Burton * 2290cad855fSPaul Burton * Scroll the current message along the LCD by one character, rearming the 2300cad855fSPaul Burton * timer if required. 2310cad855fSPaul Burton */ 232607a6301SKees Cook static void img_ascii_lcd_scroll(struct timer_list *t) 2330cad855fSPaul Burton { 234607a6301SKees Cook struct img_ascii_lcd_ctx *ctx = from_timer(ctx, t, timer); 2350cad855fSPaul Burton unsigned int i, ch = ctx->scroll_pos; 2360cad855fSPaul Burton unsigned int num_chars = ctx->cfg->num_chars; 2370cad855fSPaul Burton 2380cad855fSPaul Burton /* update the current message string */ 2390cad855fSPaul Burton for (i = 0; i < num_chars;) { 2400cad855fSPaul Burton /* copy as many characters from the string as possible */ 2410cad855fSPaul Burton for (; i < num_chars && ch < ctx->message_len; i++, ch++) 2420cad855fSPaul Burton ctx->curr[i] = ctx->message[ch]; 2430cad855fSPaul Burton 2440cad855fSPaul Burton /* wrap around to the start of the string */ 2450cad855fSPaul Burton ch = 0; 2460cad855fSPaul Burton } 2470cad855fSPaul Burton 2480cad855fSPaul Burton /* update the LCD */ 2490cad855fSPaul Burton ctx->cfg->update(ctx); 2500cad855fSPaul Burton 2510cad855fSPaul Burton /* move on to the next character */ 2520cad855fSPaul Burton ctx->scroll_pos++; 2530cad855fSPaul Burton ctx->scroll_pos %= ctx->message_len; 2540cad855fSPaul Burton 2550cad855fSPaul Burton /* rearm the timer */ 2560cad855fSPaul Burton if (ctx->message_len > ctx->cfg->num_chars) 2570cad855fSPaul Burton mod_timer(&ctx->timer, jiffies + ctx->scroll_rate); 2580cad855fSPaul Burton } 2590cad855fSPaul Burton 2600cad855fSPaul Burton /** 2610cad855fSPaul Burton * img_ascii_lcd_display() - set the message to be displayed 2620cad855fSPaul Burton * @ctx: pointer to the private data structure 2630cad855fSPaul Burton * @msg: the message to display 2640cad855fSPaul Burton * @count: length of msg, or -1 2650cad855fSPaul Burton * 2660cad855fSPaul Burton * Display a new message @msg on the LCD. @msg can be longer than the number of 2670cad855fSPaul Burton * characters the LCD can display, in which case it will begin scrolling across 2680cad855fSPaul Burton * the LCD display. 2690cad855fSPaul Burton * 2700cad855fSPaul Burton * Return: 0 on success, -ENOMEM on memory allocation failure 2710cad855fSPaul Burton */ 2720cad855fSPaul Burton static int img_ascii_lcd_display(struct img_ascii_lcd_ctx *ctx, 2730cad855fSPaul Burton const char *msg, ssize_t count) 2740cad855fSPaul Burton { 2750cad855fSPaul Burton char *new_msg; 2760cad855fSPaul Burton 2770cad855fSPaul Burton /* stop the scroll timer */ 2780cad855fSPaul Burton del_timer_sync(&ctx->timer); 2790cad855fSPaul Burton 2800cad855fSPaul Burton if (count == -1) 2810cad855fSPaul Burton count = strlen(msg); 2820cad855fSPaul Burton 2830cad855fSPaul Burton /* if the string ends with a newline, trim it */ 2840cad855fSPaul Burton if (msg[count - 1] == '\n') 2850cad855fSPaul Burton count--; 2860cad855fSPaul Burton 2870cad855fSPaul Burton new_msg = devm_kmalloc(&ctx->pdev->dev, count + 1, GFP_KERNEL); 2880cad855fSPaul Burton if (!new_msg) 2890cad855fSPaul Burton return -ENOMEM; 2900cad855fSPaul Burton 2910cad855fSPaul Burton memcpy(new_msg, msg, count); 2920cad855fSPaul Burton new_msg[count] = 0; 2930cad855fSPaul Burton 2940cad855fSPaul Burton if (ctx->message) 2950cad855fSPaul Burton devm_kfree(&ctx->pdev->dev, ctx->message); 2960cad855fSPaul Burton 2970cad855fSPaul Burton ctx->message = new_msg; 2980cad855fSPaul Burton ctx->message_len = count; 2990cad855fSPaul Burton ctx->scroll_pos = 0; 3000cad855fSPaul Burton 3010cad855fSPaul Burton /* update the LCD */ 302607a6301SKees Cook img_ascii_lcd_scroll(&ctx->timer); 3030cad855fSPaul Burton 3040cad855fSPaul Burton return 0; 3050cad855fSPaul Burton } 3060cad855fSPaul Burton 3070cad855fSPaul Burton /** 3080cad855fSPaul Burton * message_show() - read message via sysfs 3090cad855fSPaul Burton * @dev: the LCD device 3100cad855fSPaul Burton * @attr: the LCD message attribute 3110cad855fSPaul Burton * @buf: the buffer to read the message into 3120cad855fSPaul Burton * 3130cad855fSPaul Burton * Read the current message being displayed or scrolled across the LCD display 3140cad855fSPaul Burton * into @buf, for reads from sysfs. 3150cad855fSPaul Burton * 3160cad855fSPaul Burton * Return: the number of characters written to @buf 3170cad855fSPaul Burton */ 3180cad855fSPaul Burton static ssize_t message_show(struct device *dev, struct device_attribute *attr, 3190cad855fSPaul Burton char *buf) 3200cad855fSPaul Burton { 3210cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev); 3220cad855fSPaul Burton 3230cad855fSPaul Burton return sprintf(buf, "%s\n", ctx->message); 3240cad855fSPaul Burton } 3250cad855fSPaul Burton 3260cad855fSPaul Burton /** 3270cad855fSPaul Burton * message_store() - write a new message via sysfs 3280cad855fSPaul Burton * @dev: the LCD device 3290cad855fSPaul Burton * @attr: the LCD message attribute 3300cad855fSPaul Burton * @buf: the buffer containing the new message 3310cad855fSPaul Burton * @count: the size of the message in @buf 3320cad855fSPaul Burton * 3330cad855fSPaul Burton * Write a new message to display or scroll across the LCD display from sysfs. 3340cad855fSPaul Burton * 3350cad855fSPaul Burton * Return: the size of the message on success, else -ERRNO 3360cad855fSPaul Burton */ 3370cad855fSPaul Burton static ssize_t message_store(struct device *dev, struct device_attribute *attr, 3380cad855fSPaul Burton const char *buf, size_t count) 3390cad855fSPaul Burton { 3400cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx = dev_get_drvdata(dev); 3410cad855fSPaul Burton int err; 3420cad855fSPaul Burton 3430cad855fSPaul Burton err = img_ascii_lcd_display(ctx, buf, count); 3440cad855fSPaul Burton return err ?: count; 3450cad855fSPaul Burton } 3460cad855fSPaul Burton 3470cad855fSPaul Burton static DEVICE_ATTR_RW(message); 3480cad855fSPaul Burton 3490cad855fSPaul Burton /** 3500cad855fSPaul Burton * img_ascii_lcd_probe() - probe an LCD display device 3510cad855fSPaul Burton * @pdev: the LCD platform device 3520cad855fSPaul Burton * 3530cad855fSPaul Burton * Probe an LCD display device, ensuring that we have the required resources in 3540cad855fSPaul Burton * order to access the LCD & setting up private data as well as sysfs files. 3550cad855fSPaul Burton * 3560cad855fSPaul Burton * Return: 0 on success, else -ERRNO 3570cad855fSPaul Burton */ 3580cad855fSPaul Burton static int img_ascii_lcd_probe(struct platform_device *pdev) 3590cad855fSPaul Burton { 3600cad855fSPaul Burton const struct of_device_id *match; 3610cad855fSPaul Burton const struct img_ascii_lcd_config *cfg; 3620cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx; 3630cad855fSPaul Burton struct resource *res; 3640cad855fSPaul Burton int err; 3650cad855fSPaul Burton 3660cad855fSPaul Burton match = of_match_device(img_ascii_lcd_matches, &pdev->dev); 3670cad855fSPaul Burton if (!match) 3680cad855fSPaul Burton return -ENODEV; 3690cad855fSPaul Burton 3700cad855fSPaul Burton cfg = match->data; 3710cad855fSPaul Burton ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx) + cfg->num_chars, 3720cad855fSPaul Burton GFP_KERNEL); 3730cad855fSPaul Burton if (!ctx) 3740cad855fSPaul Burton return -ENOMEM; 3750cad855fSPaul Burton 3760cad855fSPaul Burton if (cfg->external_regmap) { 3770cad855fSPaul Burton ctx->regmap = syscon_node_to_regmap(pdev->dev.parent->of_node); 3780cad855fSPaul Burton if (IS_ERR(ctx->regmap)) 3790cad855fSPaul Burton return PTR_ERR(ctx->regmap); 3800cad855fSPaul Burton 3810cad855fSPaul Burton if (of_property_read_u32(pdev->dev.of_node, "offset", 3820cad855fSPaul Burton &ctx->offset)) 3830cad855fSPaul Burton return -EINVAL; 3840cad855fSPaul Burton } else { 3850cad855fSPaul Burton res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 3860cad855fSPaul Burton ctx->base = devm_ioremap_resource(&pdev->dev, res); 3870cad855fSPaul Burton if (IS_ERR(ctx->base)) 3880cad855fSPaul Burton return PTR_ERR(ctx->base); 3890cad855fSPaul Burton } 3900cad855fSPaul Burton 3910cad855fSPaul Burton ctx->pdev = pdev; 3920cad855fSPaul Burton ctx->cfg = cfg; 3930cad855fSPaul Burton ctx->message = NULL; 3940cad855fSPaul Burton ctx->scroll_pos = 0; 3950cad855fSPaul Burton ctx->scroll_rate = HZ / 2; 3960cad855fSPaul Burton 3970cad855fSPaul Burton /* initialise a timer for scrolling the message */ 398607a6301SKees Cook timer_setup(&ctx->timer, img_ascii_lcd_scroll, 0); 3990cad855fSPaul Burton 4000cad855fSPaul Burton platform_set_drvdata(pdev, ctx); 4010cad855fSPaul Burton 4020cad855fSPaul Burton /* display a default message */ 4030cad855fSPaul Burton err = img_ascii_lcd_display(ctx, "Linux " UTS_RELEASE " ", -1); 4040cad855fSPaul Burton if (err) 4050cad855fSPaul Burton goto out_del_timer; 4060cad855fSPaul Burton 4070cad855fSPaul Burton err = device_create_file(&pdev->dev, &dev_attr_message); 4080cad855fSPaul Burton if (err) 4090cad855fSPaul Burton goto out_del_timer; 4100cad855fSPaul Burton 4110cad855fSPaul Burton return 0; 4120cad855fSPaul Burton out_del_timer: 4130cad855fSPaul Burton del_timer_sync(&ctx->timer); 4140cad855fSPaul Burton return err; 4150cad855fSPaul Burton } 4160cad855fSPaul Burton 4170cad855fSPaul Burton /** 4180cad855fSPaul Burton * img_ascii_lcd_remove() - remove an LCD display device 4190cad855fSPaul Burton * @pdev: the LCD platform device 4200cad855fSPaul Burton * 4210cad855fSPaul Burton * Remove an LCD display device, freeing private resources & ensuring that the 4220cad855fSPaul Burton * driver stops using the LCD display registers. 4230cad855fSPaul Burton * 4240cad855fSPaul Burton * Return: 0 4250cad855fSPaul Burton */ 4260cad855fSPaul Burton static int img_ascii_lcd_remove(struct platform_device *pdev) 4270cad855fSPaul Burton { 4280cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev); 4290cad855fSPaul Burton 4300cad855fSPaul Burton device_remove_file(&pdev->dev, &dev_attr_message); 4310cad855fSPaul Burton del_timer_sync(&ctx->timer); 4320cad855fSPaul Burton return 0; 4330cad855fSPaul Burton } 4340cad855fSPaul Burton 4350cad855fSPaul Burton static struct platform_driver img_ascii_lcd_driver = { 4360cad855fSPaul Burton .driver = { 4370cad855fSPaul Burton .name = "img-ascii-lcd", 4380cad855fSPaul Burton .of_match_table = img_ascii_lcd_matches, 4390cad855fSPaul Burton }, 4400cad855fSPaul Burton .probe = img_ascii_lcd_probe, 4410cad855fSPaul Burton .remove = img_ascii_lcd_remove, 4420cad855fSPaul Burton }; 4430cad855fSPaul Burton module_platform_driver(img_ascii_lcd_driver); 44409c479f7SJesse Chan 44509c479f7SJesse Chan MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display"); 44609c479f7SJesse Chan MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>"); 44709c479f7SJesse Chan MODULE_LICENSE("GPL"); 448