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_address.h>
12 #include <linux/of_platform.h>
13 #include <linux/platform_device.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  * @update: function called to update the LCD
26  */
27 struct img_ascii_lcd_config {
28 	unsigned int num_chars;
29 	bool external_regmap;
30 	void (*update)(struct linedisp *linedisp);
31 };
32 
33 /**
34  * struct img_ascii_lcd_ctx - Private data structure
35  * @base: the base address of the LCD registers
36  * @regmap: the regmap through which LCD registers are accessed
37  * @offset: the offset within regmap to the start of the LCD registers
38  * @cfg: pointer to the LCD model configuration
39  * @linedisp: line display structure
40  * @curr: the string currently displayed on the LCD
41  */
42 struct img_ascii_lcd_ctx {
43 	union {
44 		void __iomem *base;
45 		struct regmap *regmap;
46 	};
47 	u32 offset;
48 	const struct img_ascii_lcd_config *cfg;
49 	struct linedisp linedisp;
50 	char curr[] __aligned(8);
51 };
52 
53 /*
54  * MIPS Boston development board
55  */
56 
57 static void boston_update(struct linedisp *linedisp)
58 {
59 	struct img_ascii_lcd_ctx *ctx =
60 		container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
61 	ulong val;
62 
63 #if BITS_PER_LONG == 64
64 	val = *((u64 *)&ctx->curr[0]);
65 	__raw_writeq(val, ctx->base);
66 #elif BITS_PER_LONG == 32
67 	val = *((u32 *)&ctx->curr[0]);
68 	__raw_writel(val, ctx->base);
69 	val = *((u32 *)&ctx->curr[4]);
70 	__raw_writel(val, ctx->base + 4);
71 #else
72 # error Not 32 or 64 bit
73 #endif
74 }
75 
76 static struct img_ascii_lcd_config boston_config = {
77 	.num_chars = 8,
78 	.update = boston_update,
79 };
80 
81 /*
82  * MIPS Malta development board
83  */
84 
85 static void malta_update(struct linedisp *linedisp)
86 {
87 	struct img_ascii_lcd_ctx *ctx =
88 		container_of(linedisp, struct img_ascii_lcd_ctx, linedisp);
89 	unsigned int i;
90 	int err = 0;
91 
92 	for (i = 0; i < linedisp->num_chars; i++) {
93 		err = regmap_write(ctx->regmap,
94 				   ctx->offset + (i * 8), ctx->curr[i]);
95 		if (err)
96 			break;
97 	}
98 
99 	if (unlikely(err))
100 		pr_err_ratelimited("Failed to update LCD display: %d\n", err);
101 }
102 
103 static struct img_ascii_lcd_config malta_config = {
104 	.num_chars = 8,
105 	.external_regmap = true,
106 	.update = malta_update,
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 				   ctx->curr[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 struct img_ascii_lcd_config sead3_config = {
204 	.num_chars = 16,
205 	.external_regmap = true,
206 	.update = sead3_update,
207 };
208 
209 static const struct of_device_id img_ascii_lcd_matches[] = {
210 	{ .compatible = "img,boston-lcd", .data = &boston_config },
211 	{ .compatible = "mti,malta-lcd", .data = &malta_config },
212 	{ .compatible = "mti,sead3-lcd", .data = &sead3_config },
213 	{ /* sentinel */ }
214 };
215 MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
216 
217 /**
218  * img_ascii_lcd_probe() - probe an LCD display device
219  * @pdev: the LCD platform device
220  *
221  * Probe an LCD display device, ensuring that we have the required resources in
222  * order to access the LCD & setting up private data as well as sysfs files.
223  *
224  * Return: 0 on success, else -ERRNO
225  */
226 static int img_ascii_lcd_probe(struct platform_device *pdev)
227 {
228 	const struct of_device_id *match;
229 	const struct img_ascii_lcd_config *cfg;
230 	struct device *dev = &pdev->dev;
231 	struct img_ascii_lcd_ctx *ctx;
232 	int err;
233 
234 	match = of_match_device(img_ascii_lcd_matches, dev);
235 	if (!match)
236 		return -ENODEV;
237 
238 	cfg = match->data;
239 	ctx = devm_kzalloc(dev, sizeof(*ctx) + cfg->num_chars, GFP_KERNEL);
240 	if (!ctx)
241 		return -ENOMEM;
242 
243 	if (cfg->external_regmap) {
244 		ctx->regmap = syscon_node_to_regmap(dev->parent->of_node);
245 		if (IS_ERR(ctx->regmap))
246 			return PTR_ERR(ctx->regmap);
247 
248 		if (of_property_read_u32(dev->of_node, "offset", &ctx->offset))
249 			return -EINVAL;
250 	} else {
251 		ctx->base = devm_platform_ioremap_resource(pdev, 0);
252 		if (IS_ERR(ctx->base))
253 			return PTR_ERR(ctx->base);
254 	}
255 
256 	err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr,
257 				cfg->update);
258 	if (err)
259 		return err;
260 
261 	/* for backwards compatibility */
262 	err = compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
263 						   &ctx->linedisp.dev.kobj,
264 						   "message", NULL);
265 	if (err)
266 		goto err_unregister;
267 
268 	platform_set_drvdata(pdev, ctx);
269 	return 0;
270 
271 err_unregister:
272 	linedisp_unregister(&ctx->linedisp);
273 	return err;
274 }
275 
276 /**
277  * img_ascii_lcd_remove() - remove an LCD display device
278  * @pdev: the LCD platform device
279  *
280  * Remove an LCD display device, freeing private resources & ensuring that the
281  * driver stops using the LCD display registers.
282  *
283  * Return: 0
284  */
285 static int img_ascii_lcd_remove(struct platform_device *pdev)
286 {
287 	struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev);
288 
289 	sysfs_remove_link(&pdev->dev.kobj, "message");
290 	linedisp_unregister(&ctx->linedisp);
291 	return 0;
292 }
293 
294 static struct platform_driver img_ascii_lcd_driver = {
295 	.driver = {
296 		.name		= "img-ascii-lcd",
297 		.of_match_table	= img_ascii_lcd_matches,
298 	},
299 	.probe	= img_ascii_lcd_probe,
300 	.remove	= img_ascii_lcd_remove,
301 };
302 module_platform_driver(img_ascii_lcd_driver);
303 
304 MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
305 MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
306 MODULE_LICENSE("GPL");
307