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>
11c52391faSRob Herring #include <linux/of.h>
120cad855fSPaul Burton #include <linux/platform_device.h>
13c52391faSRob Herring #include <linux/property.h>
140cad855fSPaul Burton #include <linux/regmap.h>
150cad855fSPaul Burton #include <linux/slab.h>
167e76aeceSGeert Uytterhoeven
177e76aeceSGeert 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
2570fb97c0SAndy Shevchenko * @ops: character line display operations
260cad855fSPaul Burton */
270cad855fSPaul Burton struct img_ascii_lcd_config {
280cad855fSPaul Burton unsigned int num_chars;
290cad855fSPaul Burton bool external_regmap;
3070fb97c0SAndy Shevchenko const struct linedisp_ops ops;
310cad855fSPaul Burton };
320cad855fSPaul Burton
330cad855fSPaul Burton /**
340cad855fSPaul Burton * struct img_ascii_lcd_ctx - Private data structure
35a8fc3d58SAndy Shevchenko * @linedisp: line display structure
360cad855fSPaul Burton * @base: the base address of the LCD registers
370cad855fSPaul Burton * @regmap: the regmap through which LCD registers are accessed
380cad855fSPaul Burton * @offset: the offset within regmap to the start of the LCD registers
390cad855fSPaul Burton * @cfg: pointer to the LCD model configuration
400cad855fSPaul Burton */
410cad855fSPaul Burton struct img_ascii_lcd_ctx {
42a8fc3d58SAndy Shevchenko struct linedisp linedisp;
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;
490cad855fSPaul Burton };
500cad855fSPaul Burton
510cad855fSPaul Burton /*
520cad855fSPaul Burton * MIPS Boston development board
530cad855fSPaul Burton */
540cad855fSPaul Burton
boston_update(struct linedisp * linedisp)557e76aeceSGeert Uytterhoeven static void boston_update(struct linedisp *linedisp)
560cad855fSPaul Burton {
577e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx =
587e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
590cad855fSPaul Burton ulong val;
600cad855fSPaul Burton
610cad855fSPaul Burton #if BITS_PER_LONG == 64
624ce026d5SAndy Shevchenko val = *((u64 *)&linedisp->buf[0]);
630cad855fSPaul Burton __raw_writeq(val, ctx->base);
640cad855fSPaul Burton #elif BITS_PER_LONG == 32
654ce026d5SAndy Shevchenko val = *((u32 *)&linedisp->buf[0]);
660cad855fSPaul Burton __raw_writel(val, ctx->base);
674ce026d5SAndy Shevchenko val = *((u32 *)&linedisp->buf[4]);
680cad855fSPaul Burton __raw_writel(val, ctx->base + 4);
690cad855fSPaul Burton #else
700cad855fSPaul Burton # error Not 32 or 64 bit
710cad855fSPaul Burton #endif
720cad855fSPaul Burton }
730cad855fSPaul Burton
740cad855fSPaul Burton static struct img_ascii_lcd_config boston_config = {
750cad855fSPaul Burton .num_chars = 8,
7670fb97c0SAndy Shevchenko .ops = {
770cad855fSPaul Burton .update = boston_update,
7870fb97c0SAndy Shevchenko },
790cad855fSPaul Burton };
800cad855fSPaul Burton
810cad855fSPaul Burton /*
820cad855fSPaul Burton * MIPS Malta development board
830cad855fSPaul Burton */
840cad855fSPaul Burton
malta_update(struct linedisp * linedisp)857e76aeceSGeert Uytterhoeven static void malta_update(struct linedisp *linedisp)
860cad855fSPaul Burton {
877e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx =
887e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
890cad855fSPaul Burton unsigned int i;
9026a2c54dSMiguel Ojeda int err = 0;
910cad855fSPaul Burton
927e76aeceSGeert Uytterhoeven for (i = 0; i < linedisp->num_chars; i++) {
930cad855fSPaul Burton err = regmap_write(ctx->regmap,
944ce026d5SAndy Shevchenko ctx->offset + (i * 8), linedisp->buf[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,
10670fb97c0SAndy Shevchenko .ops = {
1070cad855fSPaul Burton .update = malta_update,
10870fb97c0SAndy Shevchenko },
1090cad855fSPaul Burton };
1100cad855fSPaul Burton
1110cad855fSPaul Burton /*
1120cad855fSPaul Burton * MIPS SEAD3 development board
1130cad855fSPaul Burton */
1140cad855fSPaul Burton
1150cad855fSPaul Burton enum {
1160cad855fSPaul Burton SEAD3_REG_LCD_CTRL = 0x00,
1170cad855fSPaul Burton #define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7)
1180cad855fSPaul Burton SEAD3_REG_LCD_DATA = 0x08,
1190cad855fSPaul Burton SEAD3_REG_CPLD_STATUS = 0x10,
1200cad855fSPaul Burton #define SEAD3_REG_CPLD_STATUS_BUSY BIT(0)
1210cad855fSPaul Burton SEAD3_REG_CPLD_DATA = 0x18,
1220cad855fSPaul Burton #define SEAD3_REG_CPLD_DATA_BUSY BIT(7)
1230cad855fSPaul Burton };
1240cad855fSPaul Burton
sead3_wait_sm_idle(struct img_ascii_lcd_ctx * ctx)1250cad855fSPaul Burton static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx)
1260cad855fSPaul Burton {
1270cad855fSPaul Burton unsigned int status;
1280cad855fSPaul Burton int err;
1290cad855fSPaul Burton
1300cad855fSPaul Burton do {
1310cad855fSPaul Burton err = regmap_read(ctx->regmap,
1320cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_STATUS,
1330cad855fSPaul Burton &status);
1340cad855fSPaul Burton if (err)
1350cad855fSPaul Burton return err;
1360cad855fSPaul Burton } while (status & SEAD3_REG_CPLD_STATUS_BUSY);
1370cad855fSPaul Burton
1380cad855fSPaul Burton return 0;
1390cad855fSPaul Burton
1400cad855fSPaul Burton }
1410cad855fSPaul Burton
sead3_wait_lcd_idle(struct img_ascii_lcd_ctx * ctx)1420cad855fSPaul Burton static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx)
1430cad855fSPaul Burton {
1440cad855fSPaul Burton unsigned int cpld_data;
1450cad855fSPaul Burton int err;
1460cad855fSPaul Burton
1470cad855fSPaul Burton err = sead3_wait_sm_idle(ctx);
1480cad855fSPaul Burton if (err)
1490cad855fSPaul Burton return err;
1500cad855fSPaul Burton
1510cad855fSPaul Burton do {
1520cad855fSPaul Burton err = regmap_read(ctx->regmap,
1530cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL,
1540cad855fSPaul Burton &cpld_data);
1550cad855fSPaul Burton if (err)
1560cad855fSPaul Burton return err;
1570cad855fSPaul Burton
1580cad855fSPaul Burton err = sead3_wait_sm_idle(ctx);
1590cad855fSPaul Burton if (err)
1600cad855fSPaul Burton return err;
1610cad855fSPaul Burton
1620cad855fSPaul Burton err = regmap_read(ctx->regmap,
1630cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_DATA,
1640cad855fSPaul Burton &cpld_data);
1650cad855fSPaul Burton if (err)
1660cad855fSPaul Burton return err;
1670cad855fSPaul Burton } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY);
1680cad855fSPaul Burton
1690cad855fSPaul Burton return 0;
1700cad855fSPaul Burton }
1710cad855fSPaul Burton
sead3_update(struct linedisp * linedisp)1727e76aeceSGeert Uytterhoeven static void sead3_update(struct linedisp *linedisp)
1730cad855fSPaul Burton {
1747e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx =
1757e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
1760cad855fSPaul Burton unsigned int i;
17726a2c54dSMiguel Ojeda int err = 0;
1780cad855fSPaul Burton
1797e76aeceSGeert Uytterhoeven for (i = 0; i < linedisp->num_chars; i++) {
1800cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx);
1810cad855fSPaul Burton if (err)
1820cad855fSPaul Burton break;
1830cad855fSPaul Burton
1840cad855fSPaul Burton err = regmap_write(ctx->regmap,
1850cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL,
1860cad855fSPaul Burton SEAD3_REG_LCD_CTRL_SETDRAM | i);
1870cad855fSPaul Burton if (err)
1880cad855fSPaul Burton break;
1890cad855fSPaul Burton
1900cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx);
1910cad855fSPaul Burton if (err)
1920cad855fSPaul Burton break;
1930cad855fSPaul Burton
1940cad855fSPaul Burton err = regmap_write(ctx->regmap,
1950cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_DATA,
1964ce026d5SAndy Shevchenko linedisp->buf[i]);
1970cad855fSPaul Burton if (err)
1980cad855fSPaul Burton break;
1990cad855fSPaul Burton }
2000cad855fSPaul Burton
2010cad855fSPaul Burton if (unlikely(err))
2020cad855fSPaul Burton pr_err_ratelimited("Failed to update LCD display: %d\n", err);
2030cad855fSPaul Burton }
2040cad855fSPaul Burton
2050cad855fSPaul Burton static struct img_ascii_lcd_config sead3_config = {
2060cad855fSPaul Burton .num_chars = 16,
2070cad855fSPaul Burton .external_regmap = true,
20870fb97c0SAndy Shevchenko .ops = {
2090cad855fSPaul Burton .update = sead3_update,
21070fb97c0SAndy Shevchenko },
2110cad855fSPaul Burton };
2120cad855fSPaul Burton
2130cad855fSPaul Burton static const struct of_device_id img_ascii_lcd_matches[] = {
2140cad855fSPaul Burton { .compatible = "img,boston-lcd", .data = &boston_config },
2150cad855fSPaul Burton { .compatible = "mti,malta-lcd", .data = &malta_config },
2160cad855fSPaul Burton { .compatible = "mti,sead3-lcd", .data = &sead3_config },
217abda288bSDmitry Torokhov { /* sentinel */ }
2180cad855fSPaul Burton };
219750100a4SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
2200cad855fSPaul Burton
2210cad855fSPaul Burton /**
2220cad855fSPaul Burton * img_ascii_lcd_probe() - probe an LCD display device
2230cad855fSPaul Burton * @pdev: the LCD platform device
2240cad855fSPaul Burton *
2250cad855fSPaul Burton * Probe an LCD display device, ensuring that we have the required resources in
2260cad855fSPaul Burton * order to access the LCD & setting up private data as well as sysfs files.
2270cad855fSPaul Burton *
2280cad855fSPaul Burton * Return: 0 on success, else -ERRNO
2290cad855fSPaul Burton */
img_ascii_lcd_probe(struct platform_device * pdev)2300cad855fSPaul Burton static int img_ascii_lcd_probe(struct platform_device *pdev)
2310cad855fSPaul Burton {
2327b88e553SGeert Uytterhoeven struct device *dev = &pdev->dev;
233c52391faSRob Herring const struct img_ascii_lcd_config *cfg = device_get_match_data(dev);
2340cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx;
2350cad855fSPaul Burton int err;
2360cad855fSPaul Burton
2374ce026d5SAndy Shevchenko ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
2380cad855fSPaul Burton if (!ctx)
2390cad855fSPaul Burton return -ENOMEM;
2400cad855fSPaul Burton
2410cad855fSPaul Burton if (cfg->external_regmap) {
2427b88e553SGeert Uytterhoeven ctx->regmap = syscon_node_to_regmap(dev->parent->of_node);
2430cad855fSPaul Burton if (IS_ERR(ctx->regmap))
2440cad855fSPaul Burton return PTR_ERR(ctx->regmap);
2450cad855fSPaul Burton
2467b88e553SGeert Uytterhoeven if (of_property_read_u32(dev->of_node, "offset", &ctx->offset))
2470cad855fSPaul Burton return -EINVAL;
2480cad855fSPaul Burton } else {
249e8897e4fSYangtao Li ctx->base = devm_platform_ioremap_resource(pdev, 0);
2500cad855fSPaul Burton if (IS_ERR(ctx->base))
2510cad855fSPaul Burton return PTR_ERR(ctx->base);
2520cad855fSPaul Burton }
2530cad855fSPaul Burton
2544ce026d5SAndy Shevchenko err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops);
2557e76aeceSGeert Uytterhoeven if (err)
2567e76aeceSGeert Uytterhoeven return err;
2570cad855fSPaul Burton
2587e76aeceSGeert Uytterhoeven /* for backwards compatibility */
2597e76aeceSGeert Uytterhoeven err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
2607e76aeceSGeert Uytterhoeven &ctx->linedisp.dev.kobj,
2617e76aeceSGeert Uytterhoeven "message", NULL);
2627e76aeceSGeert Uytterhoeven if (err)
2637e76aeceSGeert Uytterhoeven goto err_unregister;
2640cad855fSPaul Burton
2650cad855fSPaul Burton platform_set_drvdata(pdev, ctx);
2660cad855fSPaul Burton return 0;
2677e76aeceSGeert Uytterhoeven
2687e76aeceSGeert Uytterhoeven err_unregister:
2697e76aeceSGeert Uytterhoeven linedisp_unregister(&ctx->linedisp);
2700cad855fSPaul Burton return err;
2710cad855fSPaul Burton }
2720cad855fSPaul Burton
2730cad855fSPaul Burton /**
2740cad855fSPaul Burton * img_ascii_lcd_remove() - remove an LCD display device
2750cad855fSPaul Burton * @pdev: the LCD platform device
2760cad855fSPaul Burton *
2770cad855fSPaul Burton * Remove an LCD display device, freeing private resources & ensuring that the
2780cad855fSPaul Burton * driver stops using the LCD display registers.
2790cad855fSPaul Burton */
img_ascii_lcd_remove(struct platform_device * pdev)280*5d9e1297SUwe Kleine-König static void img_ascii_lcd_remove(struct platform_device *pdev)
2810cad855fSPaul Burton {
2820cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
2830cad855fSPaul Burton
2847e76aeceSGeert Uytterhoeven sysfs_remove_link(&pdev->dev.kobj, "message");
2857e76aeceSGeert Uytterhoeven linedisp_unregister(&ctx->linedisp);
2860cad855fSPaul Burton }
2870cad855fSPaul Burton
2880cad855fSPaul Burton static struct platform_driver img_ascii_lcd_driver = {
2890cad855fSPaul Burton .driver = {
2900cad855fSPaul Burton .name = "img-ascii-lcd",
2910cad855fSPaul Burton .of_match_table = img_ascii_lcd_matches,
2920cad855fSPaul Burton },
2930cad855fSPaul Burton .probe = img_ascii_lcd_probe,
294*5d9e1297SUwe Kleine-König .remove_new = img_ascii_lcd_remove,
2950cad855fSPaul Burton };
2960cad855fSPaul Burton module_platform_driver(img_ascii_lcd_driver);
29709c479f7SJesse Chan
29809c479f7SJesse Chan MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
29909c479f7SJesse Chan MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
30009c479f7SJesse Chan MODULE_LICENSE("GPL");
301fe5bd82fSAndy Shevchenko MODULE_IMPORT_NS(LINEDISP);
302