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 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; 307e76aeceSGeert Uytterhoeven void (*update)(struct linedisp *linedisp); 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, 780cad855fSPaul Burton .update = boston_update, 790cad855fSPaul Burton }; 800cad855fSPaul Burton 810cad855fSPaul Burton /* 820cad855fSPaul Burton * MIPS Malta development board 830cad855fSPaul Burton */ 840cad855fSPaul Burton 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, 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 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 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 1707e76aeceSGeert Uytterhoeven static void sead3_update(struct linedisp *linedisp) 1710cad855fSPaul Burton { 1727e76aeceSGeert Uytterhoeven struct img_ascii_lcd_ctx *ctx = 1737e76aeceSGeert Uytterhoeven container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 1740cad855fSPaul Burton unsigned int i; 17526a2c54dSMiguel Ojeda int err = 0; 1760cad855fSPaul Burton 1777e76aeceSGeert 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 */ 2260cad855fSPaul Burton static int img_ascii_lcd_probe(struct platform_device *pdev) 2270cad855fSPaul Burton { 2287b88e553SGeert Uytterhoeven struct device *dev = &pdev->dev; 229c52391faSRob Herring const struct img_ascii_lcd_config *cfg = device_get_match_data(dev); 2300cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx; 2310cad855fSPaul Burton int err; 2320cad855fSPaul Burton 2337b88e553SGeert Uytterhoeven ctx = devm_kzalloc(dev, sizeof(*ctx) + cfg->num_chars, GFP_KERNEL); 2340cad855fSPaul Burton if (!ctx) 2350cad855fSPaul Burton return -ENOMEM; 2360cad855fSPaul Burton 2370cad855fSPaul Burton if (cfg->external_regmap) { 2387b88e553SGeert Uytterhoeven ctx->regmap = syscon_node_to_regmap(dev->parent->of_node); 2390cad855fSPaul Burton if (IS_ERR(ctx->regmap)) 2400cad855fSPaul Burton return PTR_ERR(ctx->regmap); 2410cad855fSPaul Burton 2427b88e553SGeert Uytterhoeven if (of_property_read_u32(dev->of_node, "offset", &ctx->offset)) 2430cad855fSPaul Burton return -EINVAL; 2440cad855fSPaul Burton } else { 245e8897e4fSYangtao Li ctx->base = devm_platform_ioremap_resource(pdev, 0); 2460cad855fSPaul Burton if (IS_ERR(ctx->base)) 2470cad855fSPaul Burton return PTR_ERR(ctx->base); 2480cad855fSPaul Burton } 2490cad855fSPaul Burton 2507e76aeceSGeert Uytterhoeven err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr, 2517e76aeceSGeert Uytterhoeven cfg->update); 2527e76aeceSGeert Uytterhoeven if (err) 2537e76aeceSGeert Uytterhoeven return err; 2540cad855fSPaul Burton 2557e76aeceSGeert Uytterhoeven /* for backwards compatibility */ 2567e76aeceSGeert Uytterhoeven err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj, 2577e76aeceSGeert Uytterhoeven &ctx->linedisp.dev.kobj, 2587e76aeceSGeert Uytterhoeven "message", NULL); 2597e76aeceSGeert Uytterhoeven if (err) 2607e76aeceSGeert Uytterhoeven goto err_unregister; 2610cad855fSPaul Burton 2620cad855fSPaul Burton platform_set_drvdata(pdev, ctx); 2630cad855fSPaul Burton return 0; 2647e76aeceSGeert Uytterhoeven 2657e76aeceSGeert Uytterhoeven err_unregister: 2667e76aeceSGeert Uytterhoeven linedisp_unregister(&ctx->linedisp); 2670cad855fSPaul Burton return err; 2680cad855fSPaul Burton } 2690cad855fSPaul Burton 2700cad855fSPaul Burton /** 2710cad855fSPaul Burton * img_ascii_lcd_remove() - remove an LCD display device 2720cad855fSPaul Burton * @pdev: the LCD platform device 2730cad855fSPaul Burton * 2740cad855fSPaul Burton * Remove an LCD display device, freeing private resources & ensuring that the 2750cad855fSPaul Burton * driver stops using the LCD display registers. 2760cad855fSPaul Burton * 2770cad855fSPaul Burton * Return: 0 2780cad855fSPaul Burton */ 2790cad855fSPaul Burton static int img_ascii_lcd_remove(struct platform_device *pdev) 2800cad855fSPaul Burton { 2810cad855fSPaul Burton struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev); 2820cad855fSPaul Burton 2837e76aeceSGeert Uytterhoeven sysfs_remove_link(&pdev->dev.kobj, "message"); 2847e76aeceSGeert Uytterhoeven linedisp_unregister(&ctx->linedisp); 2850cad855fSPaul Burton return 0; 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, 2940cad855fSPaul Burton .remove = 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"); 301*fe5bd82fSAndy Shevchenko MODULE_IMPORT_NS(LINEDISP); 302