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