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 25*70fb97c0SAndy 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; 30*70fb97c0SAndy 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 * @curr: the string currently displayed on the LCD 410cad855fSPaul Burton */ 420cad855fSPaul Burton struct img_ascii_lcd_ctx { 43a8fc3d58SAndy Shevchenko struct linedisp linedisp; 440cad855fSPaul Burton union { 450cad855fSPaul Burton void __iomem *base; 460cad855fSPaul Burton struct regmap *regmap; 470cad855fSPaul Burton }; 480cad855fSPaul Burton u32 offset; 490cad855fSPaul Burton const struct img_ascii_lcd_config *cfg; 500cad855fSPaul Burton char curr[] __aligned(8); 510cad855fSPaul Burton }; 520cad855fSPaul Burton 530cad855fSPaul Burton /* 540cad855fSPaul Burton * MIPS Boston development board 550cad855fSPaul Burton */ 560cad855fSPaul Burton 577e76aeceSGeert Uytterhoeven static void boston_update(struct linedisp *linedisp) 580cad855fSPaul Burton { 597e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx = 607e76aeceSGeert 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, 78*70fb97c0SAndy Shevchenko .ops = { 790cad855fSPaul Burton .update = boston_update, 80*70fb97c0SAndy Shevchenko }, 810cad855fSPaul Burton }; 820cad855fSPaul Burton 830cad855fSPaul Burton /* 840cad855fSPaul Burton * MIPS Malta development board 850cad855fSPaul Burton */ 860cad855fSPaul Burton 877e76aeceSGeert Uytterhoeven static void malta_update(struct linedisp *linedisp) 880cad855fSPaul Burton { 897e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx = 907e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 910cad855fSPaul Burton unsigned int i; 9226a2c54dSMiguel Ojeda int err = 0; 930cad855fSPaul Burton 947e76aeceSGeert Uytterhoeven for (i = 0; i < linedisp->num_chars; i++) { 950cad855fSPaul Burton err = regmap_write(ctx->regmap, 960cad855fSPaul Burton ctx->offset + (i * 8), ctx->curr[i]); 970cad855fSPaul Burton if (err) 980cad855fSPaul Burton break; 990cad855fSPaul Burton } 1000cad855fSPaul Burton 1010cad855fSPaul Burton if (unlikely(err)) 1020cad855fSPaul Burton pr_err_ratelimited("Failed to update LCD display: %d\n", err); 1030cad855fSPaul Burton } 1040cad855fSPaul Burton 1050cad855fSPaul Burton static struct img_ascii_lcd_config malta_config = { 1060cad855fSPaul Burton .num_chars = 8, 1070cad855fSPaul Burton .external_regmap = true, 108*70fb97c0SAndy Shevchenko .ops = { 1090cad855fSPaul Burton .update = malta_update, 110*70fb97c0SAndy Shevchenko }, 1110cad855fSPaul Burton }; 1120cad855fSPaul Burton 1130cad855fSPaul Burton /* 1140cad855fSPaul Burton * MIPS SEAD3 development board 1150cad855fSPaul Burton */ 1160cad855fSPaul Burton 1170cad855fSPaul Burton enum { 1180cad855fSPaul Burton SEAD3_REG_LCD_CTRL = 0x00, 1190cad855fSPaul Burton #define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7) 1200cad855fSPaul Burton SEAD3_REG_LCD_DATA = 0x08, 1210cad855fSPaul Burton SEAD3_REG_CPLD_STATUS = 0x10, 1220cad855fSPaul Burton #define SEAD3_REG_CPLD_STATUS_BUSY BIT(0) 1230cad855fSPaul Burton SEAD3_REG_CPLD_DATA = 0x18, 1240cad855fSPaul Burton #define SEAD3_REG_CPLD_DATA_BUSY BIT(7) 1250cad855fSPaul Burton }; 1260cad855fSPaul Burton 1270cad855fSPaul Burton static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx) 1280cad855fSPaul Burton { 1290cad855fSPaul Burton unsigned int status; 1300cad855fSPaul Burton int err; 1310cad855fSPaul Burton 1320cad855fSPaul Burton do { 1330cad855fSPaul Burton err = regmap_read(ctx->regmap, 1340cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_STATUS, 1350cad855fSPaul Burton &status); 1360cad855fSPaul Burton if (err) 1370cad855fSPaul Burton return err; 1380cad855fSPaul Burton } while (status & SEAD3_REG_CPLD_STATUS_BUSY); 1390cad855fSPaul Burton 1400cad855fSPaul Burton return 0; 1410cad855fSPaul Burton 1420cad855fSPaul Burton } 1430cad855fSPaul Burton 1440cad855fSPaul Burton static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx) 1450cad855fSPaul Burton { 1460cad855fSPaul Burton unsigned int cpld_data; 1470cad855fSPaul Burton int err; 1480cad855fSPaul Burton 1490cad855fSPaul Burton err = sead3_wait_sm_idle(ctx); 1500cad855fSPaul Burton if (err) 1510cad855fSPaul Burton return err; 1520cad855fSPaul Burton 1530cad855fSPaul Burton do { 1540cad855fSPaul Burton err = regmap_read(ctx->regmap, 1550cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL, 1560cad855fSPaul Burton &cpld_data); 1570cad855fSPaul Burton if (err) 1580cad855fSPaul Burton return err; 1590cad855fSPaul Burton 1600cad855fSPaul Burton err = sead3_wait_sm_idle(ctx); 1610cad855fSPaul Burton if (err) 1620cad855fSPaul Burton return err; 1630cad855fSPaul Burton 1640cad855fSPaul Burton err = regmap_read(ctx->regmap, 1650cad855fSPaul Burton ctx->offset + SEAD3_REG_CPLD_DATA, 1660cad855fSPaul Burton &cpld_data); 1670cad855fSPaul Burton if (err) 1680cad855fSPaul Burton return err; 1690cad855fSPaul Burton } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY); 1700cad855fSPaul Burton 1710cad855fSPaul Burton return 0; 1720cad855fSPaul Burton } 1730cad855fSPaul Burton 1747e76aeceSGeert Uytterhoeven static void sead3_update(struct linedisp *linedisp) 1750cad855fSPaul Burton { 1767e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx = 1777e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 1780cad855fSPaul Burton unsigned int i; 17926a2c54dSMiguel Ojeda int err = 0; 1800cad855fSPaul Burton 1817e76aeceSGeert Uytterhoeven for (i = 0; i < linedisp->num_chars; i++) { 1820cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx); 1830cad855fSPaul Burton if (err) 1840cad855fSPaul Burton break; 1850cad855fSPaul Burton 1860cad855fSPaul Burton err = regmap_write(ctx->regmap, 1870cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_CTRL, 1880cad855fSPaul Burton SEAD3_REG_LCD_CTRL_SETDRAM | i); 1890cad855fSPaul Burton if (err) 1900cad855fSPaul Burton break; 1910cad855fSPaul Burton 1920cad855fSPaul Burton err = sead3_wait_lcd_idle(ctx); 1930cad855fSPaul Burton if (err) 1940cad855fSPaul Burton break; 1950cad855fSPaul Burton 1960cad855fSPaul Burton err = regmap_write(ctx->regmap, 1970cad855fSPaul Burton ctx->offset + SEAD3_REG_LCD_DATA, 1980cad855fSPaul Burton ctx->curr[i]); 1990cad855fSPaul Burton if (err) 2000cad855fSPaul Burton break; 2010cad855fSPaul Burton } 2020cad855fSPaul Burton 2030cad855fSPaul Burton if (unlikely(err)) 2040cad855fSPaul Burton pr_err_ratelimited("Failed to update LCD display: %d\n", err); 2050cad855fSPaul Burton } 2060cad855fSPaul Burton 2070cad855fSPaul Burton static struct img_ascii_lcd_config sead3_config = { 2080cad855fSPaul Burton .num_chars = 16, 2090cad855fSPaul Burton .external_regmap = true, 210*70fb97c0SAndy Shevchenko .ops = { 2110cad855fSPaul Burton .update = sead3_update, 212*70fb97c0SAndy Shevchenko }, 2130cad855fSPaul Burton }; 2140cad855fSPaul Burton 2150cad855fSPaul Burton static const struct of_device_id img_ascii_lcd_matches[] = { 2160cad855fSPaul Burton { .compatible = "img,boston-lcd", .data = &boston_config }, 2170cad855fSPaul Burton { .compatible = "mti,malta-lcd", .data = &malta_config }, 2180cad855fSPaul Burton { .compatible = "mti,sead3-lcd", .data = &sead3_config }, 219abda288bSDmitry Torokhov { /* sentinel */ } 2200cad855fSPaul Burton }; 221750100a4SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); 2220cad855fSPaul Burton 2230cad855fSPaul Burton /** 2240cad855fSPaul Burton * img_ascii_lcd_probe() - probe an LCD display device 2250cad855fSPaul Burton * @pdev: the LCD platform device 2260cad855fSPaul Burton * 2270cad855fSPaul Burton * Probe an LCD display device, ensuring that we have the required resources in 2280cad855fSPaul Burton * order to access the LCD & setting up private data as well as sysfs files. 2290cad855fSPaul Burton * 2300cad855fSPaul Burton * Return: 0 on success, else -ERRNO 2310cad855fSPaul Burton */ 2320cad855fSPaul Burton static int img_ascii_lcd_probe(struct platform_device *pdev) 2330cad855fSPaul Burton { 2347b88e553SGeert Uytterhoeven struct device *dev = &pdev->dev; 235c52391faSRob Herring const struct img_ascii_lcd_config *cfg = device_get_match_data(dev); 2360cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx; 2370cad855fSPaul Burton int err; 2380cad855fSPaul Burton 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 2567e76aeceSGeert Uytterhoeven err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr, 257*70fb97c0SAndy Shevchenko &cfg->ops); 2587e76aeceSGeert Uytterhoeven if (err) 2597e76aeceSGeert Uytterhoeven return err; 2600cad855fSPaul Burton 2617e76aeceSGeert Uytterhoeven /* for backwards compatibility */ 2627e76aeceSGeert Uytterhoeven err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj, 2637e76aeceSGeert Uytterhoeven &ctx->linedisp.dev.kobj, 2647e76aeceSGeert Uytterhoeven "message", NULL); 2657e76aeceSGeert Uytterhoeven if (err) 2667e76aeceSGeert Uytterhoeven goto err_unregister; 2670cad855fSPaul Burton 2680cad855fSPaul Burton platform_set_drvdata(pdev, ctx); 2690cad855fSPaul Burton return 0; 2707e76aeceSGeert Uytterhoeven 2717e76aeceSGeert Uytterhoeven err_unregister: 2727e76aeceSGeert 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 */ 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 2897e76aeceSGeert Uytterhoeven sysfs_remove_link(&pdev->dev.kobj, "message"); 2907e76aeceSGeert 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"); 307fe5bd82fSAndy Shevchenko MODULE_IMPORT_NS(LINEDISP); 308