12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
20cad855fSPaul Burton /*
30cad855fSPaul Burton  * Copyright (C) 2016 Imagination Technologies
4fb615d61SPaul Burton  * Author: Paul Burton <paul.burton@mips.com>
50cad855fSPaul Burton  */
60cad855fSPaul Burton 
70cad855fSPaul Burton #include <linux/kernel.h>
80cad855fSPaul Burton #include <linux/io.h>
90cad855fSPaul Burton #include <linux/mfd/syscon.h>
100cad855fSPaul Burton #include <linux/module.h>
110cad855fSPaul Burton #include <linux/of_address.h>
120cad855fSPaul Burton #include <linux/of_platform.h>
130cad855fSPaul Burton #include <linux/platform_device.h>
140cad855fSPaul Burton #include <linux/regmap.h>
150cad855fSPaul Burton #include <linux/slab.h>
16*7e76aeceSGeert Uytterhoeven 
17*7e76aeceSGeert Uytterhoeven #include "line-display.h"
180cad855fSPaul Burton 
190cad855fSPaul Burton struct img_ascii_lcd_ctx;
200cad855fSPaul Burton 
210cad855fSPaul Burton /**
220cad855fSPaul Burton  * struct img_ascii_lcd_config - Configuration information about an LCD model
230cad855fSPaul Burton  * @num_chars: the number of characters the LCD can display
240cad855fSPaul Burton  * @external_regmap: true if registers are in a system controller, else false
250cad855fSPaul Burton  * @update: function called to update the LCD
260cad855fSPaul Burton  */
270cad855fSPaul Burton struct img_ascii_lcd_config {
280cad855fSPaul Burton 	unsigned int num_chars;
290cad855fSPaul Burton 	bool external_regmap;
30*7e76aeceSGeert Uytterhoeven 	void (*update)(struct linedisp *linedisp);
310cad855fSPaul Burton };
320cad855fSPaul Burton 
330cad855fSPaul Burton /**
340cad855fSPaul Burton  * struct img_ascii_lcd_ctx - Private data structure
350cad855fSPaul Burton  * @base: the base address of the LCD registers
360cad855fSPaul Burton  * @regmap: the regmap through which LCD registers are accessed
370cad855fSPaul Burton  * @offset: the offset within regmap to the start of the LCD registers
380cad855fSPaul Burton  * @cfg: pointer to the LCD model configuration
39*7e76aeceSGeert Uytterhoeven  * @linedisp: line display structure
400cad855fSPaul Burton  * @curr: the string currently displayed on the LCD
410cad855fSPaul Burton  */
420cad855fSPaul Burton struct img_ascii_lcd_ctx {
430cad855fSPaul Burton 	union {
440cad855fSPaul Burton 		void __iomem *base;
450cad855fSPaul Burton 		struct regmap *regmap;
460cad855fSPaul Burton 	};
470cad855fSPaul Burton 	u32 offset;
480cad855fSPaul Burton 	const struct img_ascii_lcd_config *cfg;
49*7e76aeceSGeert Uytterhoeven 	struct linedisp linedisp;
500cad855fSPaul Burton 	char curr[] __aligned(8);
510cad855fSPaul Burton };
520cad855fSPaul Burton 
530cad855fSPaul Burton /*
540cad855fSPaul Burton  * MIPS Boston development board
550cad855fSPaul Burton  */
560cad855fSPaul Burton 
boston_update(struct linedisp * linedisp)57*7e76aeceSGeert Uytterhoeven static void boston_update(struct linedisp *linedisp)
580cad855fSPaul Burton {
59*7e76aeceSGeert Uytterhoeven 	struct img_ascii_lcd_ctx *ctx =
60*7e76aeceSGeert Uytterhoeven 		container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
610cad855fSPaul Burton 	ulong val;
620cad855fSPaul Burton 
630cad855fSPaul Burton #if BITS_PER_LONG == 64
640cad855fSPaul Burton 	val = *((u64 *)&ctx->curr[0]);
650cad855fSPaul Burton 	__raw_writeq(val, ctx->base);
660cad855fSPaul Burton #elif BITS_PER_LONG == 32
670cad855fSPaul Burton 	val = *((u32 *)&ctx->curr[0]);
680cad855fSPaul Burton 	__raw_writel(val, ctx->base);
690cad855fSPaul Burton 	val = *((u32 *)&ctx->curr[4]);
700cad855fSPaul Burton 	__raw_writel(val, ctx->base + 4);
710cad855fSPaul Burton #else
720cad855fSPaul Burton # error Not 32 or 64 bit
730cad855fSPaul Burton #endif
740cad855fSPaul Burton }
750cad855fSPaul Burton 
760cad855fSPaul Burton static struct img_ascii_lcd_config boston_config = {
770cad855fSPaul Burton 	.num_chars = 8,
780cad855fSPaul Burton 	.update = boston_update,
790cad855fSPaul Burton };
800cad855fSPaul Burton 
810cad855fSPaul Burton /*
820cad855fSPaul Burton  * MIPS Malta development board
830cad855fSPaul Burton  */
840cad855fSPaul Burton 
malta_update(struct linedisp * linedisp)85*7e76aeceSGeert Uytterhoeven static void malta_update(struct linedisp *linedisp)
860cad855fSPaul Burton {
87*7e76aeceSGeert Uytterhoeven 	struct img_ascii_lcd_ctx *ctx =
88*7e76aeceSGeert Uytterhoeven 		container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
890cad855fSPaul Burton 	unsigned int i;
9026a2c54dSMiguel Ojeda 	int err = 0;
910cad855fSPaul Burton 
92*7e76aeceSGeert Uytterhoeven 	for (i = 0; i < linedisp->num_chars; i++) {
930cad855fSPaul Burton 		err = regmap_write(ctx->regmap,
940cad855fSPaul Burton 				   ctx->offset + (i * 8), ctx->curr[i]);
950cad855fSPaul Burton 		if (err)
960cad855fSPaul Burton 			break;
970cad855fSPaul Burton 	}
980cad855fSPaul Burton 
990cad855fSPaul Burton 	if (unlikely(err))
1000cad855fSPaul Burton 		pr_err_ratelimited("Failed to update LCD display: %d\n", err);
1010cad855fSPaul Burton }
1020cad855fSPaul Burton 
1030cad855fSPaul Burton static struct img_ascii_lcd_config malta_config = {
1040cad855fSPaul Burton 	.num_chars = 8,
1050cad855fSPaul Burton 	.external_regmap = true,
1060cad855fSPaul Burton 	.update = malta_update,
1070cad855fSPaul Burton };
1080cad855fSPaul Burton 
1090cad855fSPaul Burton /*
1100cad855fSPaul Burton  * MIPS SEAD3 development board
1110cad855fSPaul Burton  */
1120cad855fSPaul Burton 
1130cad855fSPaul Burton enum {
1140cad855fSPaul Burton 	SEAD3_REG_LCD_CTRL		= 0x00,
1150cad855fSPaul Burton #define SEAD3_REG_LCD_CTRL_SETDRAM	BIT(7)
1160cad855fSPaul Burton 	SEAD3_REG_LCD_DATA		= 0x08,
1170cad855fSPaul Burton 	SEAD3_REG_CPLD_STATUS		= 0x10,
1180cad855fSPaul Burton #define SEAD3_REG_CPLD_STATUS_BUSY	BIT(0)
1190cad855fSPaul Burton 	SEAD3_REG_CPLD_DATA		= 0x18,
1200cad855fSPaul Burton #define SEAD3_REG_CPLD_DATA_BUSY	BIT(7)
1210cad855fSPaul Burton };
1220cad855fSPaul Burton 
sead3_wait_sm_idle(struct img_ascii_lcd_ctx * ctx)1230cad855fSPaul Burton static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx)
1240cad855fSPaul Burton {
1250cad855fSPaul Burton 	unsigned int status;
1260cad855fSPaul Burton 	int err;
1270cad855fSPaul Burton 
1280cad855fSPaul Burton 	do {
1290cad855fSPaul Burton 		err = regmap_read(ctx->regmap,
1300cad855fSPaul Burton 				  ctx->offset + SEAD3_REG_CPLD_STATUS,
1310cad855fSPaul Burton 				  &status);
1320cad855fSPaul Burton 		if (err)
1330cad855fSPaul Burton 			return err;
1340cad855fSPaul Burton 	} while (status & SEAD3_REG_CPLD_STATUS_BUSY);
1350cad855fSPaul Burton 
1360cad855fSPaul Burton 	return 0;
1370cad855fSPaul Burton 
1380cad855fSPaul Burton }
1390cad855fSPaul Burton 
sead3_wait_lcd_idle(struct img_ascii_lcd_ctx * ctx)1400cad855fSPaul Burton static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx)
1410cad855fSPaul Burton {
1420cad855fSPaul Burton 	unsigned int cpld_data;
1430cad855fSPaul Burton 	int err;
1440cad855fSPaul Burton 
1450cad855fSPaul Burton 	err = sead3_wait_sm_idle(ctx);
1460cad855fSPaul Burton 	if (err)
1470cad855fSPaul Burton 		return err;
1480cad855fSPaul Burton 
1490cad855fSPaul Burton 	do {
1500cad855fSPaul Burton 		err = regmap_read(ctx->regmap,
1510cad855fSPaul Burton 				  ctx->offset + SEAD3_REG_LCD_CTRL,
1520cad855fSPaul Burton 				  &cpld_data);
1530cad855fSPaul Burton 		if (err)
1540cad855fSPaul Burton 			return err;
1550cad855fSPaul Burton 
1560cad855fSPaul Burton 		err = sead3_wait_sm_idle(ctx);
1570cad855fSPaul Burton 		if (err)
1580cad855fSPaul Burton 			return err;
1590cad855fSPaul Burton 
1600cad855fSPaul Burton 		err = regmap_read(ctx->regmap,
1610cad855fSPaul Burton 				  ctx->offset + SEAD3_REG_CPLD_DATA,
1620cad855fSPaul Burton 				  &cpld_data);
1630cad855fSPaul Burton 		if (err)
1640cad855fSPaul Burton 			return err;
1650cad855fSPaul Burton 	} while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY);
1660cad855fSPaul Burton 
1670cad855fSPaul Burton 	return 0;
1680cad855fSPaul Burton }
1690cad855fSPaul Burton 
sead3_update(struct linedisp * linedisp)170*7e76aeceSGeert Uytterhoeven static void sead3_update(struct linedisp *linedisp)
1710cad855fSPaul Burton {
172*7e76aeceSGeert Uytterhoeven 	struct img_ascii_lcd_ctx *ctx =
173*7e76aeceSGeert Uytterhoeven 		container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
1740cad855fSPaul Burton 	unsigned int i;
17526a2c54dSMiguel Ojeda 	int err = 0;
1760cad855fSPaul Burton 
177*7e76aeceSGeert Uytterhoeven 	for (i = 0; i < linedisp->num_chars; i++) {
1780cad855fSPaul Burton 		err = sead3_wait_lcd_idle(ctx);
1790cad855fSPaul Burton 		if (err)
1800cad855fSPaul Burton 			break;
1810cad855fSPaul Burton 
1820cad855fSPaul Burton 		err = regmap_write(ctx->regmap,
1830cad855fSPaul Burton 				   ctx->offset + SEAD3_REG_LCD_CTRL,
1840cad855fSPaul Burton 				   SEAD3_REG_LCD_CTRL_SETDRAM | i);
1850cad855fSPaul Burton 		if (err)
1860cad855fSPaul Burton 			break;
1870cad855fSPaul Burton 
1880cad855fSPaul Burton 		err = sead3_wait_lcd_idle(ctx);
1890cad855fSPaul Burton 		if (err)
1900cad855fSPaul Burton 			break;
1910cad855fSPaul Burton 
1920cad855fSPaul Burton 		err = regmap_write(ctx->regmap,
1930cad855fSPaul Burton 				   ctx->offset + SEAD3_REG_LCD_DATA,
1940cad855fSPaul Burton 				   ctx->curr[i]);
1950cad855fSPaul Burton 		if (err)
1960cad855fSPaul Burton 			break;
1970cad855fSPaul Burton 	}
1980cad855fSPaul Burton 
1990cad855fSPaul Burton 	if (unlikely(err))
2000cad855fSPaul Burton 		pr_err_ratelimited("Failed to update LCD display: %d\n", err);
2010cad855fSPaul Burton }
2020cad855fSPaul Burton 
2030cad855fSPaul Burton static struct img_ascii_lcd_config sead3_config = {
2040cad855fSPaul Burton 	.num_chars = 16,
2050cad855fSPaul Burton 	.external_regmap = true,
2060cad855fSPaul Burton 	.update = sead3_update,
2070cad855fSPaul Burton };
2080cad855fSPaul Burton 
2090cad855fSPaul Burton static const struct of_device_id img_ascii_lcd_matches[] = {
2100cad855fSPaul Burton 	{ .compatible = "img,boston-lcd", .data = &boston_config },
2110cad855fSPaul Burton 	{ .compatible = "mti,malta-lcd", .data = &malta_config },
2120cad855fSPaul Burton 	{ .compatible = "mti,sead3-lcd", .data = &sead3_config },
213abda288bSDmitry Torokhov 	{ /* sentinel */ }
2140cad855fSPaul Burton };
215750100a4SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
2160cad855fSPaul Burton 
2170cad855fSPaul Burton /**
2180cad855fSPaul Burton  * img_ascii_lcd_probe() - probe an LCD display device
2190cad855fSPaul Burton  * @pdev: the LCD platform device
2200cad855fSPaul Burton  *
2210cad855fSPaul Burton  * Probe an LCD display device, ensuring that we have the required resources in
2220cad855fSPaul Burton  * order to access the LCD & setting up private data as well as sysfs files.
2230cad855fSPaul Burton  *
2240cad855fSPaul Burton  * Return: 0 on success, else -ERRNO
2250cad855fSPaul Burton  */
img_ascii_lcd_probe(struct platform_device * pdev)2260cad855fSPaul Burton static int img_ascii_lcd_probe(struct platform_device *pdev)
2270cad855fSPaul Burton {
2280cad855fSPaul Burton 	const struct of_device_id *match;
2290cad855fSPaul Burton 	const struct img_ascii_lcd_config *cfg;
2307b88e553SGeert Uytterhoeven 	struct device *dev = &pdev->dev;
2310cad855fSPaul Burton 	struct img_ascii_lcd_ctx *ctx;
2320cad855fSPaul Burton 	int err;
2330cad855fSPaul Burton 
2347b88e553SGeert Uytterhoeven 	match = of_match_device(img_ascii_lcd_matches, dev);
2350cad855fSPaul Burton 	if (!match)
2360cad855fSPaul Burton 		return -ENODEV;
2370cad855fSPaul Burton 
2380cad855fSPaul Burton 	cfg = match->data;
2397b88e553SGeert Uytterhoeven 	ctx = devm_kzalloc(dev, sizeof(*ctx) + cfg->num_chars, GFP_KERNEL);
2400cad855fSPaul Burton 	if (!ctx)
2410cad855fSPaul Burton 		return -ENOMEM;
2420cad855fSPaul Burton 
2430cad855fSPaul Burton 	if (cfg->external_regmap) {
2447b88e553SGeert Uytterhoeven 		ctx->regmap = syscon_node_to_regmap(dev->parent->of_node);
2450cad855fSPaul Burton 		if (IS_ERR(ctx->regmap))
2460cad855fSPaul Burton 			return PTR_ERR(ctx->regmap);
2470cad855fSPaul Burton 
2487b88e553SGeert Uytterhoeven 		if (of_property_read_u32(dev->of_node, "offset", &ctx->offset))
2490cad855fSPaul Burton 			return -EINVAL;
2500cad855fSPaul Burton 	} else {
251e8897e4fSYangtao Li 		ctx->base = devm_platform_ioremap_resource(pdev, 0);
2520cad855fSPaul Burton 		if (IS_ERR(ctx->base))
2530cad855fSPaul Burton 			return PTR_ERR(ctx->base);
2540cad855fSPaul Burton 	}
2550cad855fSPaul Burton 
256*7e76aeceSGeert Uytterhoeven 	err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr,
257*7e76aeceSGeert Uytterhoeven 				cfg->update);
258*7e76aeceSGeert Uytterhoeven 	if (err)
259*7e76aeceSGeert Uytterhoeven 		return err;
2600cad855fSPaul Burton 
261*7e76aeceSGeert Uytterhoeven 	/* for backwards compatibility */
262*7e76aeceSGeert Uytterhoeven 	err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
263*7e76aeceSGeert Uytterhoeven 						   &ctx->linedisp.dev.kobj,
264*7e76aeceSGeert Uytterhoeven 						   "message", NULL);
265*7e76aeceSGeert Uytterhoeven 	if (err)
266*7e76aeceSGeert Uytterhoeven 		goto err_unregister;
2670cad855fSPaul Burton 
2680cad855fSPaul Burton 	platform_set_drvdata(pdev, ctx);
2690cad855fSPaul Burton 	return 0;
270*7e76aeceSGeert Uytterhoeven 
271*7e76aeceSGeert Uytterhoeven err_unregister:
272*7e76aeceSGeert Uytterhoeven 	linedisp_unregister(&ctx->linedisp);
2730cad855fSPaul Burton 	return err;
2740cad855fSPaul Burton }
2750cad855fSPaul Burton 
2760cad855fSPaul Burton /**
2770cad855fSPaul Burton  * img_ascii_lcd_remove() - remove an LCD display device
2780cad855fSPaul Burton  * @pdev: the LCD platform device
2790cad855fSPaul Burton  *
2800cad855fSPaul Burton  * Remove an LCD display device, freeing private resources & ensuring that the
2810cad855fSPaul Burton  * driver stops using the LCD display registers.
2820cad855fSPaul Burton  *
2830cad855fSPaul Burton  * Return: 0
2840cad855fSPaul Burton  */
img_ascii_lcd_remove(struct platform_device * pdev)2850cad855fSPaul Burton static int img_ascii_lcd_remove(struct platform_device *pdev)
2860cad855fSPaul Burton {
2870cad855fSPaul Burton 	struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
2880cad855fSPaul Burton 
289*7e76aeceSGeert Uytterhoeven 	sysfs_remove_link(&pdev->dev.kobj, "message");
290*7e76aeceSGeert Uytterhoeven 	linedisp_unregister(&ctx->linedisp);
2910cad855fSPaul Burton 	return 0;
2920cad855fSPaul Burton }
2930cad855fSPaul Burton 
2940cad855fSPaul Burton static struct platform_driver img_ascii_lcd_driver = {
2950cad855fSPaul Burton 	.driver = {
2960cad855fSPaul Burton 		.name		= "img-ascii-lcd",
2970cad855fSPaul Burton 		.of_match_table	= img_ascii_lcd_matches,
2980cad855fSPaul Burton 	},
2990cad855fSPaul Burton 	.probe	= img_ascii_lcd_probe,
3000cad855fSPaul Burton 	.remove	= img_ascii_lcd_remove,
3010cad855fSPaul Burton };
3020cad855fSPaul Burton module_platform_driver(img_ascii_lcd_driver);
30309c479f7SJesse Chan 
30409c479f7SJesse Chan MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
30509c479f7SJesse Chan MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
30609c479f7SJesse Chan MODULE_LICENSE("GPL");
307