1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2016 Imagination Technologies 4 * Author: Paul Burton <paul.burton@mips.com> 5 */ 6 7 #include <linux/kernel.h> 8 #include <linux/io.h> 9 #include <linux/mfd/syscon.h> 10 #include <linux/module.h> 11 #include <linux/of.h> 12 #include <linux/platform_device.h> 13 #include <linux/property.h> 14 #include <linux/regmap.h> 15 #include <linux/slab.h> 16 17 #include "line-display.h" 18 19 struct img_ascii_lcd_ctx; 20 21 /** 22 * struct img_ascii_lcd_config - Configuration information about an LCD model 23 * @num_chars: the number of characters the LCD can display 24 * @external_regmap: true if registers are in a system controller, else false 25 * @ops: character line display operations 26 */ 27 struct img_ascii_lcd_config { 28 unsigned int num_chars; 29 bool external_regmap; 30 const struct linedisp_ops ops; 31 }; 32 33 /** 34 * struct img_ascii_lcd_ctx - Private data structure 35 * @linedisp: line display structure 36 * @base: the base address of the LCD registers 37 * @regmap: the regmap through which LCD registers are accessed 38 * @offset: the offset within regmap to the start of the LCD registers 39 */ 40 struct img_ascii_lcd_ctx { 41 struct linedisp linedisp; 42 union { 43 void __iomem *base; 44 struct regmap *regmap; 45 }; 46 u32 offset; 47 }; 48 49 /* 50 * MIPS Boston development board 51 */ 52 53 static void boston_update(struct linedisp *linedisp) 54 { 55 struct img_ascii_lcd_ctx *ctx = 56 container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 57 ulong val; 58 59 #if BITS_PER_LONG == 64 60 val = *((u64 *)&linedisp->buf[0]); 61 __raw_writeq(val, ctx->base); 62 #elif BITS_PER_LONG == 32 63 val = *((u32 *)&linedisp->buf[0]); 64 __raw_writel(val, ctx->base); 65 val = *((u32 *)&linedisp->buf[4]); 66 __raw_writel(val, ctx->base + 4); 67 #else 68 # error Not 32 or 64 bit 69 #endif 70 } 71 72 static const struct img_ascii_lcd_config boston_config = { 73 .num_chars = 8, 74 .ops = { 75 .update = boston_update, 76 }, 77 }; 78 79 /* 80 * MIPS Malta development board 81 */ 82 83 static void malta_update(struct linedisp *linedisp) 84 { 85 struct img_ascii_lcd_ctx *ctx = 86 container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 87 unsigned int i; 88 int err = 0; 89 90 for (i = 0; i < linedisp->num_chars; i++) { 91 err = regmap_write(ctx->regmap, 92 ctx->offset + (i * 8), linedisp->buf[i]); 93 if (err) 94 break; 95 } 96 97 if (unlikely(err)) 98 pr_err_ratelimited("Failed to update LCD display: %d\n", err); 99 } 100 101 static const struct img_ascii_lcd_config malta_config = { 102 .num_chars = 8, 103 .external_regmap = true, 104 .ops = { 105 .update = malta_update, 106 }, 107 }; 108 109 /* 110 * MIPS SEAD3 development board 111 */ 112 113 enum { 114 SEAD3_REG_LCD_CTRL = 0x00, 115 #define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7) 116 SEAD3_REG_LCD_DATA = 0x08, 117 SEAD3_REG_CPLD_STATUS = 0x10, 118 #define SEAD3_REG_CPLD_STATUS_BUSY BIT(0) 119 SEAD3_REG_CPLD_DATA = 0x18, 120 #define SEAD3_REG_CPLD_DATA_BUSY BIT(7) 121 }; 122 123 static int sead3_wait_sm_idle(struct img_ascii_lcd_ctx *ctx) 124 { 125 unsigned int status; 126 int err; 127 128 do { 129 err = regmap_read(ctx->regmap, 130 ctx->offset + SEAD3_REG_CPLD_STATUS, 131 &status); 132 if (err) 133 return err; 134 } while (status & SEAD3_REG_CPLD_STATUS_BUSY); 135 136 return 0; 137 138 } 139 140 static int sead3_wait_lcd_idle(struct img_ascii_lcd_ctx *ctx) 141 { 142 unsigned int cpld_data; 143 int err; 144 145 err = sead3_wait_sm_idle(ctx); 146 if (err) 147 return err; 148 149 do { 150 err = regmap_read(ctx->regmap, 151 ctx->offset + SEAD3_REG_LCD_CTRL, 152 &cpld_data); 153 if (err) 154 return err; 155 156 err = sead3_wait_sm_idle(ctx); 157 if (err) 158 return err; 159 160 err = regmap_read(ctx->regmap, 161 ctx->offset + SEAD3_REG_CPLD_DATA, 162 &cpld_data); 163 if (err) 164 return err; 165 } while (cpld_data & SEAD3_REG_CPLD_DATA_BUSY); 166 167 return 0; 168 } 169 170 static void sead3_update(struct linedisp *linedisp) 171 { 172 struct img_ascii_lcd_ctx *ctx = 173 container_of(linedisp, struct img_ascii_lcd_ctx, linedisp); 174 unsigned int i; 175 int err = 0; 176 177 for (i = 0; i < linedisp->num_chars; i++) { 178 err = sead3_wait_lcd_idle(ctx); 179 if (err) 180 break; 181 182 err = regmap_write(ctx->regmap, 183 ctx->offset + SEAD3_REG_LCD_CTRL, 184 SEAD3_REG_LCD_CTRL_SETDRAM | i); 185 if (err) 186 break; 187 188 err = sead3_wait_lcd_idle(ctx); 189 if (err) 190 break; 191 192 err = regmap_write(ctx->regmap, 193 ctx->offset + SEAD3_REG_LCD_DATA, 194 linedisp->buf[i]); 195 if (err) 196 break; 197 } 198 199 if (unlikely(err)) 200 pr_err_ratelimited("Failed to update LCD display: %d\n", err); 201 } 202 203 static const struct img_ascii_lcd_config sead3_config = { 204 .num_chars = 16, 205 .external_regmap = true, 206 .ops = { 207 .update = sead3_update, 208 }, 209 }; 210 211 static const struct of_device_id img_ascii_lcd_matches[] = { 212 { .compatible = "img,boston-lcd", .data = &boston_config }, 213 { .compatible = "mti,malta-lcd", .data = &malta_config }, 214 { .compatible = "mti,sead3-lcd", .data = &sead3_config }, 215 { /* sentinel */ } 216 }; 217 MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); 218 219 /** 220 * img_ascii_lcd_probe() - probe an LCD display device 221 * @pdev: the LCD platform device 222 * 223 * Probe an LCD display device, ensuring that we have the required resources in 224 * order to access the LCD & setting up private data as well as sysfs files. 225 * 226 * Return: 0 on success, else -ERRNO 227 */ 228 static int img_ascii_lcd_probe(struct platform_device *pdev) 229 { 230 struct device *dev = &pdev->dev; 231 const struct img_ascii_lcd_config *cfg = device_get_match_data(dev); 232 struct img_ascii_lcd_ctx *ctx; 233 int err; 234 235 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 236 if (!ctx) 237 return -ENOMEM; 238 239 if (cfg->external_regmap) { 240 ctx->regmap = syscon_node_to_regmap(dev->parent->of_node); 241 if (IS_ERR(ctx->regmap)) 242 return PTR_ERR(ctx->regmap); 243 244 if (of_property_read_u32(dev->of_node, "offset", &ctx->offset)) 245 return -EINVAL; 246 } else { 247 ctx->base = devm_platform_ioremap_resource(pdev, 0); 248 if (IS_ERR(ctx->base)) 249 return PTR_ERR(ctx->base); 250 } 251 252 err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops); 253 if (err) 254 return err; 255 256 /* for backwards compatibility */ 257 err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj, 258 &ctx->linedisp.dev.kobj, 259 "message", NULL); 260 if (err) 261 goto err_unregister; 262 263 platform_set_drvdata(pdev, ctx); 264 return 0; 265 266 err_unregister: 267 linedisp_unregister(&ctx->linedisp); 268 return err; 269 } 270 271 /** 272 * img_ascii_lcd_remove() - remove an LCD display device 273 * @pdev: the LCD platform device 274 * 275 * Remove an LCD display device, freeing private resources & ensuring that the 276 * driver stops using the LCD display registers. 277 */ 278 static void img_ascii_lcd_remove(struct platform_device *pdev) 279 { 280 struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev); 281 282 sysfs_remove_link(&pdev->dev.kobj, "message"); 283 linedisp_unregister(&ctx->linedisp); 284 } 285 286 static struct platform_driver img_ascii_lcd_driver = { 287 .driver = { 288 .name = "img-ascii-lcd", 289 .of_match_table = img_ascii_lcd_matches, 290 }, 291 .probe = img_ascii_lcd_probe, 292 .remove = img_ascii_lcd_remove, 293 }; 294 module_platform_driver(img_ascii_lcd_driver); 295 296 MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display"); 297 MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>"); 298 MODULE_LICENSE("GPL"); 299 MODULE_IMPORT_NS("LINEDISP"); 300