lpc_fb.c revision 314503
1/*-
2 * Copyright (c) 2011 Jakub Wojciech Klama <jceel@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: stable/11/sys/arm/lpc/lpc_fb.c 314503 2017-03-01 18:53:05Z ian $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/bio.h>
33#include <sys/bus.h>
34#include <sys/conf.h>
35#include <sys/endian.h>
36#include <sys/kernel.h>
37#include <sys/kthread.h>
38#include <sys/lock.h>
39#include <sys/malloc.h>
40#include <sys/module.h>
41#include <sys/mutex.h>
42#include <sys/queue.h>
43#include <sys/resource.h>
44#include <sys/rman.h>
45#include <sys/time.h>
46#include <sys/timetc.h>
47#include <sys/watchdog.h>
48
49#include <sys/kdb.h>
50
51#include <machine/bus.h>
52#include <machine/resource.h>
53#include <machine/intr.h>
54
55#include <dev/ofw/ofw_bus.h>
56#include <dev/ofw/ofw_bus_subr.h>
57
58#include <arm/lpc/lpcreg.h>
59#include <arm/lpc/lpcvar.h>
60
61
62struct lpc_fb_dmamap_arg {
63	bus_addr_t		lf_dma_busaddr;
64};
65
66struct lpc_lcd_config {
67	int			lc_xres;
68	int			lc_yres;
69	int			lc_bpp;
70	uint32_t		lc_pixelclock;
71	int			lc_left_margin;
72	int			lc_right_margin;
73	int			lc_upper_margin;
74	int			lc_lower_margin;
75	int			lc_hsync_len;
76	int			lc_vsync_len;
77};
78
79struct lpc_fb_softc {
80	device_t		lf_dev;
81	struct cdev *		lf_cdev;
82	struct mtx		lf_mtx;
83	struct resource *	lf_mem_res;
84	struct resource *	lf_irq_res;
85	bus_space_tag_t		lf_bst;
86	bus_space_handle_t	lf_bsh;
87	void *			lf_intrhand;
88	bus_dma_tag_t		lf_dma_tag;
89	bus_dmamap_t		lf_dma_map;
90	void *			lf_buffer;
91	bus_addr_t		lf_buffer_phys;
92	bus_size_t		lf_buffer_size;
93	struct lpc_lcd_config	lf_lcd_config;
94	int			lf_initialized;
95	int			lf_opened;
96};
97
98extern void ssd1289_configure(void);
99
100#define	lpc_fb_lock(_sc)	mtx_lock(&(_sc)->lf_mtx)
101#define	lpc_fb_unlock(_sc)	mtx_unlock(&(_sc)->lf_mtx)
102#define	lpc_fb_lock_assert(sc)	mtx_assert(&(_sc)->lf_mtx, MA_OWNED)
103
104#define	lpc_fb_read_4(_sc, _reg)		\
105    bus_space_read_4((_sc)->lf_bst, (_sc)->lf_bsh, (_reg))
106#define	lpc_fb_write_4(_sc, _reg, _val)		\
107    bus_space_write_4((_sc)->lf_bst, (_sc)->lf_bsh, (_reg), (_val))
108
109
110
111static int lpc_fb_probe(device_t);
112static int lpc_fb_attach(device_t);
113static void lpc_fb_intr(void *);
114static void lpc_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err);
115
116static int lpc_fb_fdt_read(phandle_t, const char *, uint32_t *);
117static int lpc_fb_read_lcd_config(phandle_t, struct lpc_lcd_config *);
118
119static int lpc_fb_open(struct cdev *, int, int, struct thread *);
120static int lpc_fb_close(struct cdev *, int, int, struct thread *);
121static int lpc_fb_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
122static int lpc_fb_mmap(struct cdev *, vm_ooffset_t, vm_paddr_t *, int, vm_memattr_t *);
123
124static void lpc_fb_blank(struct lpc_fb_softc *);
125
126static struct cdevsw lpc_fb_cdevsw = {
127	.d_open		= lpc_fb_open,
128	.d_close	= lpc_fb_close,
129	.d_ioctl	= lpc_fb_ioctl,
130	.d_mmap		= lpc_fb_mmap,
131	.d_name		= "lpcfb",
132	.d_version	= D_VERSION,
133};
134
135static int
136lpc_fb_probe(device_t dev)
137{
138
139	if (!ofw_bus_status_okay(dev))
140		return (ENXIO);
141
142	if (!ofw_bus_is_compatible(dev, "lpc,fb"))
143		return (ENXIO);
144
145	device_set_desc(dev, "LPC32x0 framebuffer device");
146	return (BUS_PROBE_DEFAULT);
147}
148
149static int
150lpc_fb_attach(device_t dev)
151{
152	struct lpc_fb_softc *sc = device_get_softc(dev);
153	struct lpc_fb_dmamap_arg ctx;
154	phandle_t node;
155	int mode, rid, err = 0;
156
157	sc->lf_dev = dev;
158	mtx_init(&sc->lf_mtx, "lpcfb", "fb", MTX_DEF);
159
160	rid = 0;
161	sc->lf_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
162	    RF_ACTIVE);
163	if (!sc->lf_mem_res) {
164		device_printf(dev, "cannot allocate memory window\n");
165		return (ENXIO);
166	}
167
168	sc->lf_bst = rman_get_bustag(sc->lf_mem_res);
169	sc->lf_bsh = rman_get_bushandle(sc->lf_mem_res);
170
171	rid = 0;
172	sc->lf_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
173	    RF_ACTIVE);
174	if (!sc->lf_irq_res) {
175		device_printf(dev, "cannot allocate interrupt\n");
176		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
177		return (ENXIO);
178	}
179
180	if (bus_setup_intr(dev, sc->lf_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
181	    NULL, lpc_fb_intr, sc, &sc->lf_intrhand))
182	{
183		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lf_mem_res);
184		bus_release_resource(dev, SYS_RES_IRQ, 1, sc->lf_irq_res);
185		device_printf(dev, "cannot setup interrupt handler\n");
186		return (ENXIO);
187	}
188
189	node = ofw_bus_get_node(dev);
190
191	err = lpc_fb_read_lcd_config(node, &sc->lf_lcd_config);
192	if (err) {
193		device_printf(dev, "cannot read LCD configuration\n");
194		goto fail;
195	}
196
197	sc->lf_buffer_size = sc->lf_lcd_config.lc_xres *
198	    sc->lf_lcd_config.lc_yres *
199	    (sc->lf_lcd_config.lc_bpp == 24 ? 3 : 2);
200
201	device_printf(dev, "%dx%d LCD, %d bits per pixel, %dkHz pixel clock\n",
202	    sc->lf_lcd_config.lc_xres, sc->lf_lcd_config.lc_yres,
203	    sc->lf_lcd_config.lc_bpp, sc->lf_lcd_config.lc_pixelclock / 1000);
204
205	err = bus_dma_tag_create(
206	    bus_get_dma_tag(sc->lf_dev),
207	    4, 0,			/* alignment, boundary */
208	    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
209	    BUS_SPACE_MAXADDR,		/* highaddr */
210	    NULL, NULL,			/* filter, filterarg */
211	    sc->lf_buffer_size, 1,	/* maxsize, nsegments */
212	    sc->lf_buffer_size, 0,	/* maxsegsize, flags */
213	    NULL, NULL,			/* lockfunc, lockarg */
214	    &sc->lf_dma_tag);
215
216	err = bus_dmamem_alloc(sc->lf_dma_tag, (void **)&sc->lf_buffer,
217	    0, &sc->lf_dma_map);
218	if (err) {
219		device_printf(dev, "cannot allocate framebuffer\n");
220		goto fail;
221	}
222
223	err = bus_dmamap_load(sc->lf_dma_tag, sc->lf_dma_map, sc->lf_buffer,
224	    sc->lf_buffer_size, lpc_fb_dmamap_cb, &ctx, BUS_DMA_NOWAIT);
225	if (err) {
226		device_printf(dev, "cannot load DMA map\n");
227		goto fail;
228	}
229
230	switch (sc->lf_lcd_config.lc_bpp) {
231	case 12:
232		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_12;
233		break;
234	case 15:
235		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_15;
236		break;
237	case 16:
238		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_16;
239		break;
240	case 24:
241		mode = LPC_CLKPWR_LCDCLK_CTRL_MODE_24;
242		break;
243	default:
244		panic("unsupported bpp");
245	}
246
247	lpc_pwr_write(sc->lf_dev, LPC_CLKPWR_LCDCLK_CTRL,
248	    LPC_CLKPWR_LCDCLK_CTRL_MODE(mode) |
249	    LPC_CLKPWR_LCDCLK_CTRL_HCLKEN);
250
251	sc->lf_buffer_phys = ctx.lf_dma_busaddr;
252	sc->lf_cdev = make_dev(&lpc_fb_cdevsw, 0, UID_ROOT, GID_WHEEL,
253	    0600, "lpcfb");
254
255	sc->lf_cdev->si_drv1 = sc;
256
257	return (0);
258fail:
259	return (ENXIO);
260}
261
262static void
263lpc_fb_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
264{
265	struct lpc_fb_dmamap_arg *ctx;
266
267	if (err)
268		return;
269
270	ctx = (struct lpc_fb_dmamap_arg *)arg;
271	ctx->lf_dma_busaddr = segs[0].ds_addr;
272}
273
274static void
275lpc_fb_intr(void *arg)
276{
277}
278
279static int
280lpc_fb_fdt_read(phandle_t node, const char *name, uint32_t *ret)
281{
282	if (OF_getencprop(node, name, ret, sizeof(uint32_t)) <= 0)
283		return (ENOENT);
284
285	return (0);
286}
287
288static int
289lpc_fb_read_lcd_config(phandle_t node, struct lpc_lcd_config *cfg)
290{
291	if (lpc_fb_fdt_read(node, "horizontal-resolution", &cfg->lc_xres))
292		return (ENXIO);
293
294	if (lpc_fb_fdt_read(node, "vertical-resolution", &cfg->lc_yres))
295		return (ENXIO);
296
297	if (lpc_fb_fdt_read(node, "bits-per-pixel", &cfg->lc_bpp))
298		return (ENXIO);
299
300	if (lpc_fb_fdt_read(node, "pixel-clock", &cfg->lc_pixelclock))
301		return (ENXIO);
302
303	if (lpc_fb_fdt_read(node, "left-margin", &cfg->lc_left_margin))
304		return (ENXIO);
305
306	if (lpc_fb_fdt_read(node, "right-margin", &cfg->lc_right_margin))
307		return (ENXIO);
308
309	if (lpc_fb_fdt_read(node, "upper-margin", &cfg->lc_upper_margin))
310		return (ENXIO);
311
312	if (lpc_fb_fdt_read(node, "lower-margin", &cfg->lc_lower_margin))
313		return (ENXIO);
314
315	if (lpc_fb_fdt_read(node, "hsync-len", &cfg->lc_hsync_len))
316		return (ENXIO);
317
318	if (lpc_fb_fdt_read(node, "vsync-len", &cfg->lc_vsync_len))
319		return (ENXIO);
320
321	return (0);
322}
323
324static void
325lpc_fb_setup(struct lpc_fb_softc *sc)
326{
327	struct lpc_lcd_config *cfg = &sc->lf_lcd_config;
328	uint32_t bpp;
329
330	lpc_fb_write_4(sc, LPC_LCD_TIMH,
331	    LPC_LCD_TIMH_PPL(cfg->lc_xres) |
332	    LPC_LCD_TIMH_HSW(cfg->lc_hsync_len - 1) |
333	    LPC_LCD_TIMH_HFP(cfg->lc_right_margin - 1) |
334	    LPC_LCD_TIMH_HBP(cfg->lc_left_margin - 1));
335
336	lpc_fb_write_4(sc, LPC_LCD_TIMV,
337	    LPC_LCD_TIMV_LPP(cfg->lc_yres - 1) |
338	    LPC_LCD_TIMV_VSW(cfg->lc_vsync_len - 1) |
339	    LPC_LCD_TIMV_VFP(cfg->lc_lower_margin) |
340	    LPC_LCD_TIMV_VBP(cfg->lc_upper_margin));
341
342	/* XXX LPC_LCD_POL_PCD_LO */
343	lpc_fb_write_4(sc, LPC_LCD_POL,
344	    LPC_LCD_POL_IHS | LPC_LCD_POL_IVS |
345	    LPC_LCD_POL_CPL(cfg->lc_xres - 1) |
346	    LPC_LCD_POL_PCD_LO(4));
347
348	lpc_fb_write_4(sc, LPC_LCD_UPBASE, sc->lf_buffer_phys);
349
350	switch (cfg->lc_bpp) {
351	case 1:
352		bpp = LPC_LCD_CTRL_BPP1;
353		break;
354	case 2:
355		bpp = LPC_LCD_CTRL_BPP2;
356		break;
357	case 4:
358		bpp = LPC_LCD_CTRL_BPP4;
359		break;
360	case 8:
361		bpp = LPC_LCD_CTRL_BPP8;
362		break;
363	case 12:
364		bpp = LPC_LCD_CTRL_BPP12_444;
365		break;
366	case 15:
367		bpp = LPC_LCD_CTRL_BPP16;
368		break;
369	case 16:
370		bpp = LPC_LCD_CTRL_BPP16_565;
371		break;
372	case 24:
373		bpp = LPC_LCD_CTRL_BPP24;
374		break;
375	default:
376		panic("LCD unknown bpp: %d", cfg->lc_bpp);
377	}
378
379	lpc_fb_write_4(sc, LPC_LCD_CTRL,
380	    LPC_LCD_CTRL_LCDVCOMP(1) |
381	    LPC_LCD_CTRL_LCDPWR |
382	    LPC_LCD_CTRL_BGR |
383	    LPC_LCD_CTRL_LCDTFT |
384	    LPC_LCD_CTRL_LCDBPP(bpp) |
385	    LPC_LCD_CTRL_LCDEN);
386}
387
388
389static int
390lpc_fb_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
391{
392	struct lpc_fb_softc *sc = cdev->si_drv1;
393
394	lpc_fb_lock(sc);
395
396	if (sc->lf_opened)
397		return (EBUSY);
398
399	sc->lf_opened = 1;
400
401	lpc_fb_unlock(sc);
402
403	if (!sc->lf_initialized) {
404		ssd1289_configure();
405		lpc_fb_setup(sc);
406		lpc_fb_blank(sc);
407		sc->lf_initialized = 1;
408	}
409
410	return (0);
411}
412
413static int
414lpc_fb_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
415{
416	struct lpc_fb_softc *sc = cdev->si_drv1;
417
418	lpc_fb_lock(sc);
419	sc->lf_opened = 0;
420	lpc_fb_unlock(sc);
421
422	return (0);
423}
424
425static int
426lpc_fb_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int x,
427    struct thread *td)
428{
429
430	return (EINVAL);
431}
432
433static int
434lpc_fb_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr,
435    int nprot, vm_memattr_t *memattr)
436{
437	struct lpc_fb_softc *sc = cdev->si_drv1;
438
439	*paddr = (vm_paddr_t)(sc->lf_buffer_phys + offset);
440	return (0);
441}
442
443static void
444lpc_fb_blank(struct lpc_fb_softc *sc)
445{
446	memset(sc->lf_buffer, 0xffff, sc->lf_buffer_size);
447}
448
449static device_method_t lpc_fb_methods[] = {
450	/* Device interface */
451	DEVMETHOD(device_probe,		lpc_fb_probe),
452	DEVMETHOD(device_attach,	lpc_fb_attach),
453
454	{ 0, 0 }
455};
456
457static devclass_t lpc_fb_devclass;
458
459static driver_t lpc_fb_driver = {
460	"lpcfb",
461	lpc_fb_methods,
462	sizeof(struct lpc_fb_softc),
463};
464
465DRIVER_MODULE(lpcfb, simplebus, lpc_fb_driver, lpc_fb_devclass, 0, 0);
466