1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Simplest possible simple frame-buffer driver, as a platform device |
4 | * |
5 | * Copyright (c) 2013, Stephen Warren |
6 | * |
7 | * Based on q40fb.c, which was: |
8 | * Copyright (C) 2001 Richard Zidlicky <rz@linux-m68k.org> |
9 | * |
10 | * Also based on offb.c, which was: |
11 | * Copyright (C) 1997 Geert Uytterhoeven |
12 | * Copyright (C) 1996 Paul Mackerras |
13 | */ |
14 | |
15 | #include <linux/aperture.h> |
16 | #include <linux/errno.h> |
17 | #include <linux/fb.h> |
18 | #include <linux/io.h> |
19 | #include <linux/module.h> |
20 | #include <linux/platform_data/simplefb.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/clk.h> |
23 | #include <linux/of.h> |
24 | #include <linux/of_address.h> |
25 | #include <linux/of_clk.h> |
26 | #include <linux/of_platform.h> |
27 | #include <linux/parser.h> |
28 | #include <linux/pm_domain.h> |
29 | #include <linux/regulator/consumer.h> |
30 | |
31 | static const struct fb_fix_screeninfo simplefb_fix = { |
32 | .id = "simple" , |
33 | .type = FB_TYPE_PACKED_PIXELS, |
34 | .visual = FB_VISUAL_TRUECOLOR, |
35 | .accel = FB_ACCEL_NONE, |
36 | }; |
37 | |
38 | static const struct fb_var_screeninfo simplefb_var = { |
39 | .height = -1, |
40 | .width = -1, |
41 | .activate = FB_ACTIVATE_NOW, |
42 | .vmode = FB_VMODE_NONINTERLACED, |
43 | }; |
44 | |
45 | #define PSEUDO_PALETTE_SIZE 16 |
46 | |
47 | static int simplefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, |
48 | u_int transp, struct fb_info *info) |
49 | { |
50 | u32 *pal = info->pseudo_palette; |
51 | u32 cr = red >> (16 - info->var.red.length); |
52 | u32 cg = green >> (16 - info->var.green.length); |
53 | u32 cb = blue >> (16 - info->var.blue.length); |
54 | u32 value; |
55 | |
56 | if (regno >= PSEUDO_PALETTE_SIZE) |
57 | return -EINVAL; |
58 | |
59 | value = (cr << info->var.red.offset) | |
60 | (cg << info->var.green.offset) | |
61 | (cb << info->var.blue.offset); |
62 | if (info->var.transp.length > 0) { |
63 | u32 mask = (1 << info->var.transp.length) - 1; |
64 | mask <<= info->var.transp.offset; |
65 | value |= mask; |
66 | } |
67 | pal[regno] = value; |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | struct simplefb_par { |
73 | u32 palette[PSEUDO_PALETTE_SIZE]; |
74 | resource_size_t base; |
75 | resource_size_t size; |
76 | struct resource *mem; |
77 | #if defined CONFIG_OF && defined CONFIG_COMMON_CLK |
78 | bool clks_enabled; |
79 | unsigned int clk_count; |
80 | struct clk **clks; |
81 | #endif |
82 | #if defined CONFIG_OF && defined CONFIG_PM_GENERIC_DOMAINS |
83 | unsigned int num_genpds; |
84 | struct device **genpds; |
85 | struct device_link **genpd_links; |
86 | #endif |
87 | #if defined CONFIG_OF && defined CONFIG_REGULATOR |
88 | bool regulators_enabled; |
89 | u32 regulator_count; |
90 | struct regulator **regulators; |
91 | #endif |
92 | }; |
93 | |
94 | static void simplefb_clocks_destroy(struct simplefb_par *par); |
95 | static void simplefb_regulators_destroy(struct simplefb_par *par); |
96 | |
97 | /* |
98 | * fb_ops.fb_destroy is called by the last put_fb_info() call at the end |
99 | * of unregister_framebuffer() or fb_release(). Do any cleanup here. |
100 | */ |
101 | static void simplefb_destroy(struct fb_info *info) |
102 | { |
103 | struct simplefb_par *par = info->par; |
104 | struct resource *mem = par->mem; |
105 | |
106 | simplefb_regulators_destroy(par: info->par); |
107 | simplefb_clocks_destroy(par: info->par); |
108 | if (info->screen_base) |
109 | iounmap(addr: info->screen_base); |
110 | |
111 | framebuffer_release(info); |
112 | |
113 | if (mem) |
114 | release_mem_region(mem->start, resource_size(mem)); |
115 | } |
116 | |
117 | static const struct fb_ops simplefb_ops = { |
118 | .owner = THIS_MODULE, |
119 | FB_DEFAULT_IOMEM_OPS, |
120 | .fb_destroy = simplefb_destroy, |
121 | .fb_setcolreg = simplefb_setcolreg, |
122 | }; |
123 | |
124 | static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS; |
125 | |
126 | struct simplefb_params { |
127 | u32 width; |
128 | u32 height; |
129 | u32 stride; |
130 | struct simplefb_format *format; |
131 | struct resource memory; |
132 | }; |
133 | |
134 | static int simplefb_parse_dt(struct platform_device *pdev, |
135 | struct simplefb_params *params) |
136 | { |
137 | struct device_node *np = pdev->dev.of_node, *mem; |
138 | int ret; |
139 | const char *format; |
140 | int i; |
141 | |
142 | ret = of_property_read_u32(np, propname: "width" , out_value: ¶ms->width); |
143 | if (ret) { |
144 | dev_err(&pdev->dev, "Can't parse width property\n" ); |
145 | return ret; |
146 | } |
147 | |
148 | ret = of_property_read_u32(np, propname: "height" , out_value: ¶ms->height); |
149 | if (ret) { |
150 | dev_err(&pdev->dev, "Can't parse height property\n" ); |
151 | return ret; |
152 | } |
153 | |
154 | ret = of_property_read_u32(np, propname: "stride" , out_value: ¶ms->stride); |
155 | if (ret) { |
156 | dev_err(&pdev->dev, "Can't parse stride property\n" ); |
157 | return ret; |
158 | } |
159 | |
160 | ret = of_property_read_string(np, propname: "format" , out_string: &format); |
161 | if (ret) { |
162 | dev_err(&pdev->dev, "Can't parse format property\n" ); |
163 | return ret; |
164 | } |
165 | params->format = NULL; |
166 | for (i = 0; i < ARRAY_SIZE(simplefb_formats); i++) { |
167 | if (strcmp(format, simplefb_formats[i].name)) |
168 | continue; |
169 | params->format = &simplefb_formats[i]; |
170 | break; |
171 | } |
172 | if (!params->format) { |
173 | dev_err(&pdev->dev, "Invalid format value\n" ); |
174 | return -EINVAL; |
175 | } |
176 | |
177 | mem = of_parse_phandle(np, phandle_name: "memory-region" , index: 0); |
178 | if (mem) { |
179 | ret = of_address_to_resource(dev: mem, index: 0, r: ¶ms->memory); |
180 | if (ret < 0) { |
181 | dev_err(&pdev->dev, "failed to parse memory-region\n" ); |
182 | of_node_put(node: mem); |
183 | return ret; |
184 | } |
185 | |
186 | if (of_property_present(np, propname: "reg" )) |
187 | dev_warn(&pdev->dev, "preferring \"memory-region\" over \"reg\" property\n" ); |
188 | |
189 | of_node_put(node: mem); |
190 | } else { |
191 | memset(¶ms->memory, 0, sizeof(params->memory)); |
192 | } |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | static int simplefb_parse_pd(struct platform_device *pdev, |
198 | struct simplefb_params *params) |
199 | { |
200 | struct simplefb_platform_data *pd = dev_get_platdata(dev: &pdev->dev); |
201 | int i; |
202 | |
203 | params->width = pd->width; |
204 | params->height = pd->height; |
205 | params->stride = pd->stride; |
206 | |
207 | params->format = NULL; |
208 | for (i = 0; i < ARRAY_SIZE(simplefb_formats); i++) { |
209 | if (strcmp(pd->format, simplefb_formats[i].name)) |
210 | continue; |
211 | |
212 | params->format = &simplefb_formats[i]; |
213 | break; |
214 | } |
215 | |
216 | if (!params->format) { |
217 | dev_err(&pdev->dev, "Invalid format value\n" ); |
218 | return -EINVAL; |
219 | } |
220 | |
221 | memset(¶ms->memory, 0, sizeof(params->memory)); |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | #if defined CONFIG_OF && defined CONFIG_COMMON_CLK |
227 | /* |
228 | * Clock handling code. |
229 | * |
230 | * Here we handle the clocks property of our "simple-framebuffer" dt node. |
231 | * This is necessary so that we can make sure that any clocks needed by |
232 | * the display engine that the bootloader set up for us (and for which it |
233 | * provided a simplefb dt node), stay up, for the life of the simplefb |
234 | * driver. |
235 | * |
236 | * When the driver unloads, we cleanly disable, and then release the clocks. |
237 | * |
238 | * We only complain about errors here, no action is taken as the most likely |
239 | * error can only happen due to a mismatch between the bootloader which set |
240 | * up simplefb, and the clock definitions in the device tree. Chances are |
241 | * that there are no adverse effects, and if there are, a clean teardown of |
242 | * the fb probe will not help us much either. So just complain and carry on, |
243 | * and hope that the user actually gets a working fb at the end of things. |
244 | */ |
245 | static int simplefb_clocks_get(struct simplefb_par *par, |
246 | struct platform_device *pdev) |
247 | { |
248 | struct device_node *np = pdev->dev.of_node; |
249 | struct clk *clock; |
250 | int i; |
251 | |
252 | if (dev_get_platdata(dev: &pdev->dev) || !np) |
253 | return 0; |
254 | |
255 | par->clk_count = of_clk_get_parent_count(np); |
256 | if (!par->clk_count) |
257 | return 0; |
258 | |
259 | par->clks = kcalloc(n: par->clk_count, size: sizeof(struct clk *), GFP_KERNEL); |
260 | if (!par->clks) |
261 | return -ENOMEM; |
262 | |
263 | for (i = 0; i < par->clk_count; i++) { |
264 | clock = of_clk_get(np, index: i); |
265 | if (IS_ERR(ptr: clock)) { |
266 | if (PTR_ERR(ptr: clock) == -EPROBE_DEFER) { |
267 | while (--i >= 0) { |
268 | clk_put(clk: par->clks[i]); |
269 | } |
270 | kfree(objp: par->clks); |
271 | return -EPROBE_DEFER; |
272 | } |
273 | dev_err(&pdev->dev, "%s: clock %d not found: %ld\n" , |
274 | __func__, i, PTR_ERR(clock)); |
275 | continue; |
276 | } |
277 | par->clks[i] = clock; |
278 | } |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static void simplefb_clocks_enable(struct simplefb_par *par, |
284 | struct platform_device *pdev) |
285 | { |
286 | int i, ret; |
287 | |
288 | for (i = 0; i < par->clk_count; i++) { |
289 | if (par->clks[i]) { |
290 | ret = clk_prepare_enable(clk: par->clks[i]); |
291 | if (ret) { |
292 | dev_err(&pdev->dev, |
293 | "%s: failed to enable clock %d: %d\n" , |
294 | __func__, i, ret); |
295 | clk_put(clk: par->clks[i]); |
296 | par->clks[i] = NULL; |
297 | } |
298 | } |
299 | } |
300 | par->clks_enabled = true; |
301 | } |
302 | |
303 | static void simplefb_clocks_destroy(struct simplefb_par *par) |
304 | { |
305 | int i; |
306 | |
307 | if (!par->clks) |
308 | return; |
309 | |
310 | for (i = 0; i < par->clk_count; i++) { |
311 | if (par->clks[i]) { |
312 | if (par->clks_enabled) |
313 | clk_disable_unprepare(clk: par->clks[i]); |
314 | clk_put(clk: par->clks[i]); |
315 | } |
316 | } |
317 | |
318 | kfree(objp: par->clks); |
319 | } |
320 | #else |
321 | static int simplefb_clocks_get(struct simplefb_par *par, |
322 | struct platform_device *pdev) { return 0; } |
323 | static void simplefb_clocks_enable(struct simplefb_par *par, |
324 | struct platform_device *pdev) { } |
325 | static void simplefb_clocks_destroy(struct simplefb_par *par) { } |
326 | #endif |
327 | |
328 | #if defined CONFIG_OF && defined CONFIG_REGULATOR |
329 | |
330 | #define SUPPLY_SUFFIX "-supply" |
331 | |
332 | /* |
333 | * Regulator handling code. |
334 | * |
335 | * Here we handle the num-supplies and vin*-supply properties of our |
336 | * "simple-framebuffer" dt node. This is necessary so that we can make sure |
337 | * that any regulators needed by the display hardware that the bootloader |
338 | * set up for us (and for which it provided a simplefb dt node), stay up, |
339 | * for the life of the simplefb driver. |
340 | * |
341 | * When the driver unloads, we cleanly disable, and then release the |
342 | * regulators. |
343 | * |
344 | * We only complain about errors here, no action is taken as the most likely |
345 | * error can only happen due to a mismatch between the bootloader which set |
346 | * up simplefb, and the regulator definitions in the device tree. Chances are |
347 | * that there are no adverse effects, and if there are, a clean teardown of |
348 | * the fb probe will not help us much either. So just complain and carry on, |
349 | * and hope that the user actually gets a working fb at the end of things. |
350 | */ |
351 | static int simplefb_regulators_get(struct simplefb_par *par, |
352 | struct platform_device *pdev) |
353 | { |
354 | struct device_node *np = pdev->dev.of_node; |
355 | struct property *prop; |
356 | struct regulator *regulator; |
357 | const char *p; |
358 | int count = 0, i = 0; |
359 | |
360 | if (dev_get_platdata(dev: &pdev->dev) || !np) |
361 | return 0; |
362 | |
363 | /* Count the number of regulator supplies */ |
364 | for_each_property_of_node(np, prop) { |
365 | p = strstr(prop->name, SUPPLY_SUFFIX); |
366 | if (p && p != prop->name) |
367 | count++; |
368 | } |
369 | |
370 | if (!count) |
371 | return 0; |
372 | |
373 | par->regulators = devm_kcalloc(dev: &pdev->dev, n: count, |
374 | size: sizeof(struct regulator *), GFP_KERNEL); |
375 | if (!par->regulators) |
376 | return -ENOMEM; |
377 | |
378 | /* Get all the regulators */ |
379 | for_each_property_of_node(np, prop) { |
380 | char name[32]; /* 32 is max size of property name */ |
381 | |
382 | p = strstr(prop->name, SUPPLY_SUFFIX); |
383 | if (!p || p == prop->name) |
384 | continue; |
385 | |
386 | strscpy(name, prop->name, |
387 | strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1); |
388 | regulator = devm_regulator_get_optional(dev: &pdev->dev, id: name); |
389 | if (IS_ERR(ptr: regulator)) { |
390 | if (PTR_ERR(ptr: regulator) == -EPROBE_DEFER) |
391 | return -EPROBE_DEFER; |
392 | dev_err(&pdev->dev, "regulator %s not found: %ld\n" , |
393 | name, PTR_ERR(regulator)); |
394 | continue; |
395 | } |
396 | par->regulators[i++] = regulator; |
397 | } |
398 | par->regulator_count = i; |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | static void simplefb_regulators_enable(struct simplefb_par *par, |
404 | struct platform_device *pdev) |
405 | { |
406 | int i, ret; |
407 | |
408 | /* Enable all the regulators */ |
409 | for (i = 0; i < par->regulator_count; i++) { |
410 | ret = regulator_enable(regulator: par->regulators[i]); |
411 | if (ret) { |
412 | dev_err(&pdev->dev, |
413 | "failed to enable regulator %d: %d\n" , |
414 | i, ret); |
415 | devm_regulator_put(regulator: par->regulators[i]); |
416 | par->regulators[i] = NULL; |
417 | } |
418 | } |
419 | par->regulators_enabled = true; |
420 | } |
421 | |
422 | static void simplefb_regulators_destroy(struct simplefb_par *par) |
423 | { |
424 | int i; |
425 | |
426 | if (!par->regulators || !par->regulators_enabled) |
427 | return; |
428 | |
429 | for (i = 0; i < par->regulator_count; i++) |
430 | if (par->regulators[i]) |
431 | regulator_disable(regulator: par->regulators[i]); |
432 | } |
433 | #else |
434 | static int simplefb_regulators_get(struct simplefb_par *par, |
435 | struct platform_device *pdev) { return 0; } |
436 | static void simplefb_regulators_enable(struct simplefb_par *par, |
437 | struct platform_device *pdev) { } |
438 | static void simplefb_regulators_destroy(struct simplefb_par *par) { } |
439 | #endif |
440 | |
441 | #if defined CONFIG_OF && defined CONFIG_PM_GENERIC_DOMAINS |
442 | static void simplefb_detach_genpds(void *res) |
443 | { |
444 | struct simplefb_par *par = res; |
445 | unsigned int i = par->num_genpds; |
446 | |
447 | if (par->num_genpds <= 1) |
448 | return; |
449 | |
450 | while (i--) { |
451 | if (par->genpd_links[i]) |
452 | device_link_del(link: par->genpd_links[i]); |
453 | |
454 | if (!IS_ERR_OR_NULL(ptr: par->genpds[i])) |
455 | dev_pm_domain_detach(dev: par->genpds[i], power_off: true); |
456 | } |
457 | } |
458 | |
459 | static int simplefb_attach_genpds(struct simplefb_par *par, |
460 | struct platform_device *pdev) |
461 | { |
462 | struct device *dev = &pdev->dev; |
463 | unsigned int i; |
464 | int err; |
465 | |
466 | err = of_count_phandle_with_args(np: dev->of_node, list_name: "power-domains" , |
467 | cells_name: "#power-domain-cells" ); |
468 | if (err < 0) { |
469 | /* Nothing wrong if optional PDs are missing */ |
470 | if (err == -ENOENT) |
471 | return 0; |
472 | |
473 | dev_err(dev, "failed to parse power-domains: %d\n" , err); |
474 | return err; |
475 | } |
476 | |
477 | par->num_genpds = err; |
478 | |
479 | /* |
480 | * Single power-domain devices are handled by the driver core, so |
481 | * nothing to do here. |
482 | */ |
483 | if (par->num_genpds <= 1) |
484 | return 0; |
485 | |
486 | par->genpds = devm_kcalloc(dev, n: par->num_genpds, size: sizeof(*par->genpds), |
487 | GFP_KERNEL); |
488 | if (!par->genpds) |
489 | return -ENOMEM; |
490 | |
491 | par->genpd_links = devm_kcalloc(dev, n: par->num_genpds, |
492 | size: sizeof(*par->genpd_links), |
493 | GFP_KERNEL); |
494 | if (!par->genpd_links) |
495 | return -ENOMEM; |
496 | |
497 | for (i = 0; i < par->num_genpds; i++) { |
498 | par->genpds[i] = dev_pm_domain_attach_by_id(dev, index: i); |
499 | if (IS_ERR(ptr: par->genpds[i])) { |
500 | err = PTR_ERR(ptr: par->genpds[i]); |
501 | if (err == -EPROBE_DEFER) { |
502 | simplefb_detach_genpds(res: par); |
503 | return err; |
504 | } |
505 | |
506 | dev_warn(dev, "failed to attach domain %u: %d\n" , i, err); |
507 | continue; |
508 | } |
509 | |
510 | par->genpd_links[i] = device_link_add(consumer: dev, supplier: par->genpds[i], |
511 | DL_FLAG_STATELESS | |
512 | DL_FLAG_PM_RUNTIME | |
513 | DL_FLAG_RPM_ACTIVE); |
514 | if (!par->genpd_links[i]) |
515 | dev_warn(dev, "failed to link power-domain %u\n" , i); |
516 | } |
517 | |
518 | return devm_add_action_or_reset(dev, simplefb_detach_genpds, par); |
519 | } |
520 | #else |
521 | static int simplefb_attach_genpds(struct simplefb_par *par, |
522 | struct platform_device *pdev) |
523 | { |
524 | return 0; |
525 | } |
526 | #endif |
527 | |
528 | static int simplefb_probe(struct platform_device *pdev) |
529 | { |
530 | int ret; |
531 | struct simplefb_params params; |
532 | struct fb_info *info; |
533 | struct simplefb_par *par; |
534 | struct resource *res, *mem; |
535 | |
536 | if (fb_get_options(name: "simplefb" , NULL)) |
537 | return -ENODEV; |
538 | |
539 | ret = -ENODEV; |
540 | if (dev_get_platdata(dev: &pdev->dev)) |
541 | ret = simplefb_parse_pd(pdev, params: ¶ms); |
542 | else if (pdev->dev.of_node) |
543 | ret = simplefb_parse_dt(pdev, params: ¶ms); |
544 | |
545 | if (ret) |
546 | return ret; |
547 | |
548 | if (params.memory.start == 0 && params.memory.end == 0) { |
549 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
550 | if (!res) { |
551 | dev_err(&pdev->dev, "No memory resource\n" ); |
552 | return -EINVAL; |
553 | } |
554 | } else { |
555 | res = ¶ms.memory; |
556 | } |
557 | |
558 | mem = request_mem_region(res->start, resource_size(res), "simplefb" ); |
559 | if (!mem) { |
560 | /* |
561 | * We cannot make this fatal. Sometimes this comes from magic |
562 | * spaces our resource handlers simply don't know about. Use |
563 | * the I/O-memory resource as-is and try to map that instead. |
564 | */ |
565 | dev_warn(&pdev->dev, "simplefb: cannot reserve video memory at %pR\n" , res); |
566 | mem = res; |
567 | } |
568 | |
569 | info = framebuffer_alloc(size: sizeof(struct simplefb_par), dev: &pdev->dev); |
570 | if (!info) { |
571 | ret = -ENOMEM; |
572 | goto error_release_mem_region; |
573 | } |
574 | platform_set_drvdata(pdev, data: info); |
575 | |
576 | par = info->par; |
577 | |
578 | info->fix = simplefb_fix; |
579 | info->fix.smem_start = mem->start; |
580 | info->fix.smem_len = resource_size(res: mem); |
581 | info->fix.line_length = params.stride; |
582 | |
583 | info->var = simplefb_var; |
584 | info->var.xres = params.width; |
585 | info->var.yres = params.height; |
586 | info->var.xres_virtual = params.width; |
587 | info->var.yres_virtual = params.height; |
588 | info->var.bits_per_pixel = params.format->bits_per_pixel; |
589 | info->var.red = params.format->red; |
590 | info->var.green = params.format->green; |
591 | info->var.blue = params.format->blue; |
592 | info->var.transp = params.format->transp; |
593 | |
594 | par->base = info->fix.smem_start; |
595 | par->size = info->fix.smem_len; |
596 | |
597 | info->fbops = &simplefb_ops; |
598 | info->screen_base = ioremap_wc(offset: info->fix.smem_start, |
599 | size: info->fix.smem_len); |
600 | if (!info->screen_base) { |
601 | ret = -ENOMEM; |
602 | goto error_fb_release; |
603 | } |
604 | info->pseudo_palette = par->palette; |
605 | |
606 | ret = simplefb_clocks_get(par, pdev); |
607 | if (ret < 0) |
608 | goto error_unmap; |
609 | |
610 | ret = simplefb_regulators_get(par, pdev); |
611 | if (ret < 0) |
612 | goto error_clocks; |
613 | |
614 | ret = simplefb_attach_genpds(par, pdev); |
615 | if (ret < 0) |
616 | goto error_regulators; |
617 | |
618 | simplefb_clocks_enable(par, pdev); |
619 | simplefb_regulators_enable(par, pdev); |
620 | |
621 | dev_info(&pdev->dev, "framebuffer at 0x%lx, 0x%x bytes\n" , |
622 | info->fix.smem_start, info->fix.smem_len); |
623 | dev_info(&pdev->dev, "format=%s, mode=%dx%dx%d, linelength=%d\n" , |
624 | params.format->name, |
625 | info->var.xres, info->var.yres, |
626 | info->var.bits_per_pixel, info->fix.line_length); |
627 | |
628 | if (mem != res) |
629 | par->mem = mem; /* release in clean-up handler */ |
630 | |
631 | ret = devm_aperture_acquire_for_platform_device(pdev, base: par->base, size: par->size); |
632 | if (ret) { |
633 | dev_err(&pdev->dev, "Unable to acquire aperture: %d\n" , ret); |
634 | goto error_regulators; |
635 | } |
636 | ret = register_framebuffer(fb_info: info); |
637 | if (ret < 0) { |
638 | dev_err(&pdev->dev, "Unable to register simplefb: %d\n" , ret); |
639 | goto error_regulators; |
640 | } |
641 | |
642 | dev_info(&pdev->dev, "fb%d: simplefb registered!\n" , info->node); |
643 | |
644 | return 0; |
645 | |
646 | error_regulators: |
647 | simplefb_regulators_destroy(par); |
648 | error_clocks: |
649 | simplefb_clocks_destroy(par); |
650 | error_unmap: |
651 | iounmap(addr: info->screen_base); |
652 | error_fb_release: |
653 | framebuffer_release(info); |
654 | error_release_mem_region: |
655 | if (mem != res) |
656 | release_mem_region(mem->start, resource_size(mem)); |
657 | return ret; |
658 | } |
659 | |
660 | static void simplefb_remove(struct platform_device *pdev) |
661 | { |
662 | struct fb_info *info = platform_get_drvdata(pdev); |
663 | |
664 | /* simplefb_destroy takes care of info cleanup */ |
665 | unregister_framebuffer(fb_info: info); |
666 | } |
667 | |
668 | static const struct of_device_id simplefb_of_match[] = { |
669 | { .compatible = "simple-framebuffer" , }, |
670 | { }, |
671 | }; |
672 | MODULE_DEVICE_TABLE(of, simplefb_of_match); |
673 | |
674 | static struct platform_driver simplefb_driver = { |
675 | .driver = { |
676 | .name = "simple-framebuffer" , |
677 | .of_match_table = simplefb_of_match, |
678 | }, |
679 | .probe = simplefb_probe, |
680 | .remove_new = simplefb_remove, |
681 | }; |
682 | |
683 | module_platform_driver(simplefb_driver); |
684 | |
685 | MODULE_AUTHOR("Stephen Warren <swarren@wwwdotorg.org>" ); |
686 | MODULE_DESCRIPTION("Simple framebuffer driver" ); |
687 | MODULE_LICENSE("GPL v2" ); |
688 | |