1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Cobalt/SEAD3 LCD frame buffer driver. |
4 | * |
5 | * Copyright (C) 2008 Yoichi Yuasa <yuasa@linux-mips.org> |
6 | * Copyright (C) 2012 MIPS Technologies, Inc. |
7 | */ |
8 | #include <linux/delay.h> |
9 | #include <linux/fb.h> |
10 | #include <linux/init.h> |
11 | #include <linux/io.h> |
12 | #include <linux/ioport.h> |
13 | #include <linux/uaccess.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/module.h> |
16 | #include <linux/sched/signal.h> |
17 | |
18 | /* |
19 | * Cursor position address |
20 | * \X 0 1 2 ... 14 15 |
21 | * Y+----+----+----+---+----+----+ |
22 | * 0|0x00|0x01|0x02|...|0x0e|0x0f| |
23 | * +----+----+----+---+----+----+ |
24 | * 1|0x40|0x41|0x42|...|0x4e|0x4f| |
25 | * +----+----+----+---+----+----+ |
26 | */ |
27 | #define LCD_DATA_REG_OFFSET 0x10 |
28 | #define LCD_XRES_MAX 16 |
29 | #define LCD_YRES_MAX 2 |
30 | #define LCD_CHARS_MAX 32 |
31 | |
32 | #define LCD_CLEAR 0x01 |
33 | #define LCD_CURSOR_MOVE_HOME 0x02 |
34 | #define LCD_RESET 0x06 |
35 | #define LCD_OFF 0x08 |
36 | #define LCD_CURSOR_OFF 0x0c |
37 | #define LCD_CURSOR_BLINK_OFF 0x0e |
38 | #define LCD_CURSOR_ON 0x0f |
39 | #define LCD_ON LCD_CURSOR_ON |
40 | #define LCD_CURSOR_MOVE_LEFT 0x10 |
41 | #define LCD_CURSOR_MOVE_RIGHT 0x14 |
42 | #define LCD_DISPLAY_LEFT 0x18 |
43 | #define LCD_DISPLAY_RIGHT 0x1c |
44 | #define LCD_PRERESET 0x3f /* execute 4 times continuously */ |
45 | #define LCD_BUSY 0x80 |
46 | |
47 | #define LCD_GRAPHIC_MODE 0x40 |
48 | #define LCD_TEXT_MODE 0x80 |
49 | #define LCD_CUR_POS_MASK 0x7f |
50 | |
51 | #define LCD_CUR_POS(x) ((x) & LCD_CUR_POS_MASK) |
52 | #define LCD_TEXT_POS(x) ((x) | LCD_TEXT_MODE) |
53 | |
54 | static inline void lcd_write_control(struct fb_info *info, u8 control) |
55 | { |
56 | writel(val: (u32)control << 24, addr: info->screen_base); |
57 | } |
58 | |
59 | static inline u8 lcd_read_control(struct fb_info *info) |
60 | { |
61 | return readl(addr: info->screen_base) >> 24; |
62 | } |
63 | |
64 | static inline void lcd_write_data(struct fb_info *info, u8 data) |
65 | { |
66 | writel(val: (u32)data << 24, addr: info->screen_base + LCD_DATA_REG_OFFSET); |
67 | } |
68 | |
69 | static inline u8 lcd_read_data(struct fb_info *info) |
70 | { |
71 | return readl(addr: info->screen_base + LCD_DATA_REG_OFFSET) >> 24; |
72 | } |
73 | |
74 | static int lcd_busy_wait(struct fb_info *info) |
75 | { |
76 | u8 val = 0; |
77 | int timeout = 10, retval = 0; |
78 | |
79 | do { |
80 | val = lcd_read_control(info); |
81 | val &= LCD_BUSY; |
82 | if (val != LCD_BUSY) |
83 | break; |
84 | |
85 | if (msleep_interruptible(msecs: 1)) |
86 | return -EINTR; |
87 | |
88 | timeout--; |
89 | } while (timeout); |
90 | |
91 | if (val == LCD_BUSY) |
92 | retval = -EBUSY; |
93 | |
94 | return retval; |
95 | } |
96 | |
97 | static void lcd_clear(struct fb_info *info) |
98 | { |
99 | int i; |
100 | |
101 | for (i = 0; i < 4; i++) { |
102 | udelay(150); |
103 | |
104 | lcd_write_control(info, LCD_PRERESET); |
105 | } |
106 | |
107 | udelay(150); |
108 | |
109 | lcd_write_control(info, LCD_CLEAR); |
110 | |
111 | udelay(150); |
112 | |
113 | lcd_write_control(info, LCD_RESET); |
114 | } |
115 | |
116 | static const struct fb_fix_screeninfo cobalt_lcdfb_fix = { |
117 | .id = "cobalt-lcd" , |
118 | .type = FB_TYPE_TEXT, |
119 | .type_aux = FB_AUX_TEXT_MDA, |
120 | .visual = FB_VISUAL_MONO01, |
121 | .line_length = LCD_XRES_MAX, |
122 | .accel = FB_ACCEL_NONE, |
123 | }; |
124 | |
125 | static ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf, |
126 | size_t count, loff_t *ppos) |
127 | { |
128 | char src[LCD_CHARS_MAX]; |
129 | unsigned long pos; |
130 | int len, retval = 0; |
131 | |
132 | if (!info->screen_base) |
133 | return -ENODEV; |
134 | |
135 | pos = *ppos; |
136 | if (pos >= LCD_CHARS_MAX || count == 0) |
137 | return 0; |
138 | |
139 | if (count > LCD_CHARS_MAX) |
140 | count = LCD_CHARS_MAX; |
141 | |
142 | if (pos + count > LCD_CHARS_MAX) |
143 | count = LCD_CHARS_MAX - pos; |
144 | |
145 | for (len = 0; len < count; len++) { |
146 | retval = lcd_busy_wait(info); |
147 | if (retval < 0) |
148 | break; |
149 | |
150 | lcd_write_control(info, LCD_TEXT_POS(pos)); |
151 | |
152 | retval = lcd_busy_wait(info); |
153 | if (retval < 0) |
154 | break; |
155 | |
156 | src[len] = lcd_read_data(info); |
157 | if (pos == 0x0f) |
158 | pos = 0x40; |
159 | else |
160 | pos++; |
161 | } |
162 | |
163 | if (retval < 0 && signal_pending(current)) |
164 | return -ERESTARTSYS; |
165 | |
166 | if (copy_to_user(to: buf, from: src, n: len)) |
167 | return -EFAULT; |
168 | |
169 | *ppos += len; |
170 | |
171 | return len; |
172 | } |
173 | |
174 | static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf, |
175 | size_t count, loff_t *ppos) |
176 | { |
177 | char dst[LCD_CHARS_MAX]; |
178 | unsigned long pos; |
179 | int len, retval = 0; |
180 | |
181 | if (!info->screen_base) |
182 | return -ENODEV; |
183 | |
184 | pos = *ppos; |
185 | if (pos >= LCD_CHARS_MAX || count == 0) |
186 | return 0; |
187 | |
188 | if (count > LCD_CHARS_MAX) |
189 | count = LCD_CHARS_MAX; |
190 | |
191 | if (pos + count > LCD_CHARS_MAX) |
192 | count = LCD_CHARS_MAX - pos; |
193 | |
194 | if (copy_from_user(to: dst, from: buf, n: count)) |
195 | return -EFAULT; |
196 | |
197 | for (len = 0; len < count; len++) { |
198 | retval = lcd_busy_wait(info); |
199 | if (retval < 0) |
200 | break; |
201 | |
202 | lcd_write_control(info, LCD_TEXT_POS(pos)); |
203 | |
204 | retval = lcd_busy_wait(info); |
205 | if (retval < 0) |
206 | break; |
207 | |
208 | lcd_write_data(info, data: dst[len]); |
209 | if (pos == 0x0f) |
210 | pos = 0x40; |
211 | else |
212 | pos++; |
213 | } |
214 | |
215 | if (retval < 0 && signal_pending(current)) |
216 | return -ERESTARTSYS; |
217 | |
218 | *ppos += len; |
219 | |
220 | return len; |
221 | } |
222 | |
223 | static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info) |
224 | { |
225 | int retval; |
226 | |
227 | retval = lcd_busy_wait(info); |
228 | if (retval < 0) |
229 | return retval; |
230 | |
231 | switch (blank_mode) { |
232 | case FB_BLANK_UNBLANK: |
233 | lcd_write_control(info, LCD_ON); |
234 | break; |
235 | default: |
236 | lcd_write_control(info, LCD_OFF); |
237 | break; |
238 | } |
239 | |
240 | return 0; |
241 | } |
242 | |
243 | static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor) |
244 | { |
245 | u32 x, y; |
246 | int retval; |
247 | |
248 | switch (cursor->set) { |
249 | case FB_CUR_SETPOS: |
250 | x = cursor->image.dx; |
251 | y = cursor->image.dy; |
252 | if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX) |
253 | return -EINVAL; |
254 | |
255 | retval = lcd_busy_wait(info); |
256 | if (retval < 0) |
257 | return retval; |
258 | |
259 | lcd_write_control(info, |
260 | LCD_TEXT_POS(info->fix.line_length * y + x)); |
261 | break; |
262 | default: |
263 | return -EINVAL; |
264 | } |
265 | |
266 | retval = lcd_busy_wait(info); |
267 | if (retval < 0) |
268 | return retval; |
269 | |
270 | if (cursor->enable) |
271 | lcd_write_control(info, LCD_CURSOR_ON); |
272 | else |
273 | lcd_write_control(info, LCD_CURSOR_OFF); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static const struct fb_ops cobalt_lcd_fbops = { |
279 | .owner = THIS_MODULE, |
280 | .fb_read = cobalt_lcdfb_read, |
281 | .fb_write = cobalt_lcdfb_write, |
282 | .fb_blank = cobalt_lcdfb_blank, |
283 | __FB_DEFAULT_IOMEM_OPS_DRAW, |
284 | .fb_cursor = cobalt_lcdfb_cursor, |
285 | __FB_DEFAULT_IOMEM_OPS_MMAP, |
286 | }; |
287 | |
288 | static int cobalt_lcdfb_probe(struct platform_device *dev) |
289 | { |
290 | struct fb_info *info; |
291 | struct resource *res; |
292 | int retval; |
293 | |
294 | info = framebuffer_alloc(size: 0, dev: &dev->dev); |
295 | if (!info) |
296 | return -ENOMEM; |
297 | |
298 | res = platform_get_resource(dev, IORESOURCE_MEM, 0); |
299 | if (!res) { |
300 | framebuffer_release(info); |
301 | return -EBUSY; |
302 | } |
303 | |
304 | info->screen_size = resource_size(res); |
305 | info->screen_base = devm_ioremap(dev: &dev->dev, offset: res->start, |
306 | size: info->screen_size); |
307 | if (!info->screen_base) { |
308 | framebuffer_release(info); |
309 | return -ENOMEM; |
310 | } |
311 | |
312 | info->fbops = &cobalt_lcd_fbops; |
313 | info->fix = cobalt_lcdfb_fix; |
314 | info->fix.smem_start = res->start; |
315 | info->fix.smem_len = info->screen_size; |
316 | info->pseudo_palette = NULL; |
317 | info->par = NULL; |
318 | |
319 | retval = register_framebuffer(fb_info: info); |
320 | if (retval < 0) { |
321 | framebuffer_release(info); |
322 | return retval; |
323 | } |
324 | |
325 | platform_set_drvdata(pdev: dev, data: info); |
326 | |
327 | lcd_clear(info); |
328 | |
329 | fb_info(info, "Cobalt server LCD frame buffer device\n" ); |
330 | |
331 | return 0; |
332 | } |
333 | |
334 | static void cobalt_lcdfb_remove(struct platform_device *dev) |
335 | { |
336 | struct fb_info *info; |
337 | |
338 | info = platform_get_drvdata(pdev: dev); |
339 | if (info) { |
340 | unregister_framebuffer(fb_info: info); |
341 | framebuffer_release(info); |
342 | } |
343 | } |
344 | |
345 | static struct platform_driver cobalt_lcdfb_driver = { |
346 | .probe = cobalt_lcdfb_probe, |
347 | .remove_new = cobalt_lcdfb_remove, |
348 | .driver = { |
349 | .name = "cobalt-lcd" , |
350 | }, |
351 | }; |
352 | module_platform_driver(cobalt_lcdfb_driver); |
353 | |
354 | MODULE_LICENSE("GPL v2" ); |
355 | MODULE_AUTHOR("Yoichi Yuasa" ); |
356 | MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver" ); |
357 | |