162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* sbuslib.c: Helper library for SBUS framebuffer drivers.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2003 David S. Miller (davem@redhat.com)
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/compat.h>
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/string.h>
1162306a36Sopenharmony_ci#include <linux/fb.h>
1262306a36Sopenharmony_ci#include <linux/mm.h>
1362306a36Sopenharmony_ci#include <linux/uaccess.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/fbio.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "sbuslib.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_civoid sbusfb_fill_var(struct fb_var_screeninfo *var, struct device_node *dp,
2162306a36Sopenharmony_ci		     int bpp)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	memset(var, 0, sizeof(*var));
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	var->xres = of_getintprop_default(dp, "width", 1152);
2662306a36Sopenharmony_ci	var->yres = of_getintprop_default(dp, "height", 900);
2762306a36Sopenharmony_ci	var->xres_virtual = var->xres;
2862306a36Sopenharmony_ci	var->yres_virtual = var->yres;
2962306a36Sopenharmony_ci	var->bits_per_pixel = bpp;
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciEXPORT_SYMBOL(sbusfb_fill_var);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic unsigned long sbusfb_mmapsize(long size, unsigned long fbsize)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	if (size == SBUS_MMAP_EMPTY) return 0;
3762306a36Sopenharmony_ci	if (size >= 0) return size;
3862306a36Sopenharmony_ci	return fbsize * (-size);
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciint sbusfb_mmap_helper(struct sbus_mmap_map *map,
4262306a36Sopenharmony_ci		       unsigned long physbase,
4362306a36Sopenharmony_ci		       unsigned long fbsize,
4462306a36Sopenharmony_ci		       unsigned long iospace,
4562306a36Sopenharmony_ci		       struct vm_area_struct *vma)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	unsigned int size, page, r, map_size;
4862306a36Sopenharmony_ci	unsigned long map_offset = 0;
4962306a36Sopenharmony_ci	unsigned long off;
5062306a36Sopenharmony_ci	int i;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (!(vma->vm_flags & (VM_SHARED | VM_MAYSHARE)))
5362306a36Sopenharmony_ci		return -EINVAL;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	size = vma->vm_end - vma->vm_start;
5662306a36Sopenharmony_ci	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
5762306a36Sopenharmony_ci		return -EINVAL;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	off = vma->vm_pgoff << PAGE_SHIFT;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* Each page, see which map applies */
6662306a36Sopenharmony_ci	for (page = 0; page < size; ){
6762306a36Sopenharmony_ci		map_size = 0;
6862306a36Sopenharmony_ci		for (i = 0; map[i].size; i++)
6962306a36Sopenharmony_ci			if (map[i].voff == off+page) {
7062306a36Sopenharmony_ci				map_size = sbusfb_mmapsize(map[i].size, fbsize);
7162306a36Sopenharmony_ci#ifdef __sparc_v9__
7262306a36Sopenharmony_ci#define POFF_MASK	(PAGE_MASK|0x1UL)
7362306a36Sopenharmony_ci#else
7462306a36Sopenharmony_ci#define POFF_MASK	(PAGE_MASK)
7562306a36Sopenharmony_ci#endif
7662306a36Sopenharmony_ci				map_offset = (physbase + map[i].poff) & POFF_MASK;
7762306a36Sopenharmony_ci				break;
7862306a36Sopenharmony_ci			}
7962306a36Sopenharmony_ci		if (!map_size) {
8062306a36Sopenharmony_ci			page += PAGE_SIZE;
8162306a36Sopenharmony_ci			continue;
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci		if (page + map_size > size)
8462306a36Sopenharmony_ci			map_size = size - page;
8562306a36Sopenharmony_ci		r = io_remap_pfn_range(vma,
8662306a36Sopenharmony_ci					vma->vm_start + page,
8762306a36Sopenharmony_ci					MK_IOSPACE_PFN(iospace,
8862306a36Sopenharmony_ci						map_offset >> PAGE_SHIFT),
8962306a36Sopenharmony_ci					map_size,
9062306a36Sopenharmony_ci					vma->vm_page_prot);
9162306a36Sopenharmony_ci		if (r)
9262306a36Sopenharmony_ci			return -EAGAIN;
9362306a36Sopenharmony_ci		page += map_size;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	return 0;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ciEXPORT_SYMBOL(sbusfb_mmap_helper);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ciint sbusfb_ioctl_helper(unsigned long cmd, unsigned long arg,
10162306a36Sopenharmony_ci			struct fb_info *info,
10262306a36Sopenharmony_ci			int type, int fb_depth, unsigned long fb_size)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	switch(cmd) {
10562306a36Sopenharmony_ci	case FBIOGTYPE: {
10662306a36Sopenharmony_ci		struct fbtype __user *f = (struct fbtype __user *) arg;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		if (put_user(type, &f->fb_type) ||
10962306a36Sopenharmony_ci		    put_user(info->var.yres, &f->fb_height) ||
11062306a36Sopenharmony_ci		    put_user(info->var.xres, &f->fb_width) ||
11162306a36Sopenharmony_ci		    put_user(fb_depth, &f->fb_depth) ||
11262306a36Sopenharmony_ci		    put_user(0, &f->fb_cmsize) ||
11362306a36Sopenharmony_ci		    put_user(fb_size, &f->fb_cmsize))
11462306a36Sopenharmony_ci			return -EFAULT;
11562306a36Sopenharmony_ci		return 0;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	case FBIOPUTCMAP_SPARC: {
11862306a36Sopenharmony_ci		struct fbcmap __user *c = (struct fbcmap __user *) arg;
11962306a36Sopenharmony_ci		struct fb_cmap cmap;
12062306a36Sopenharmony_ci		u16 red, green, blue;
12162306a36Sopenharmony_ci		u8 red8, green8, blue8;
12262306a36Sopenharmony_ci		unsigned char __user *ured;
12362306a36Sopenharmony_ci		unsigned char __user *ugreen;
12462306a36Sopenharmony_ci		unsigned char __user *ublue;
12562306a36Sopenharmony_ci		unsigned int index, count, i;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		if (get_user(index, &c->index) ||
12862306a36Sopenharmony_ci		    get_user(count, &c->count) ||
12962306a36Sopenharmony_ci		    get_user(ured, &c->red) ||
13062306a36Sopenharmony_ci		    get_user(ugreen, &c->green) ||
13162306a36Sopenharmony_ci		    get_user(ublue, &c->blue))
13262306a36Sopenharmony_ci			return -EFAULT;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		cmap.len = 1;
13562306a36Sopenharmony_ci		cmap.red = &red;
13662306a36Sopenharmony_ci		cmap.green = &green;
13762306a36Sopenharmony_ci		cmap.blue = &blue;
13862306a36Sopenharmony_ci		cmap.transp = NULL;
13962306a36Sopenharmony_ci		for (i = 0; i < count; i++) {
14062306a36Sopenharmony_ci			int err;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci			if (get_user(red8, &ured[i]) ||
14362306a36Sopenharmony_ci			    get_user(green8, &ugreen[i]) ||
14462306a36Sopenharmony_ci			    get_user(blue8, &ublue[i]))
14562306a36Sopenharmony_ci				return -EFAULT;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci			red = red8 << 8;
14862306a36Sopenharmony_ci			green = green8 << 8;
14962306a36Sopenharmony_ci			blue = blue8 << 8;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci			cmap.start = index + i;
15262306a36Sopenharmony_ci			err = fb_set_cmap(&cmap, info);
15362306a36Sopenharmony_ci			if (err)
15462306a36Sopenharmony_ci				return err;
15562306a36Sopenharmony_ci		}
15662306a36Sopenharmony_ci		return 0;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	case FBIOGETCMAP_SPARC: {
15962306a36Sopenharmony_ci		struct fbcmap __user *c = (struct fbcmap __user *) arg;
16062306a36Sopenharmony_ci		unsigned char __user *ured;
16162306a36Sopenharmony_ci		unsigned char __user *ugreen;
16262306a36Sopenharmony_ci		unsigned char __user *ublue;
16362306a36Sopenharmony_ci		struct fb_cmap *cmap = &info->cmap;
16462306a36Sopenharmony_ci		unsigned int index, count, i;
16562306a36Sopenharmony_ci		u8 red, green, blue;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		if (get_user(index, &c->index) ||
16862306a36Sopenharmony_ci		    get_user(count, &c->count) ||
16962306a36Sopenharmony_ci		    get_user(ured, &c->red) ||
17062306a36Sopenharmony_ci		    get_user(ugreen, &c->green) ||
17162306a36Sopenharmony_ci		    get_user(ublue, &c->blue))
17262306a36Sopenharmony_ci			return -EFAULT;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		if (index > cmap->len || count > cmap->len - index)
17562306a36Sopenharmony_ci			return -EINVAL;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci		for (i = 0; i < count; i++) {
17862306a36Sopenharmony_ci			red = cmap->red[index + i] >> 8;
17962306a36Sopenharmony_ci			green = cmap->green[index + i] >> 8;
18062306a36Sopenharmony_ci			blue = cmap->blue[index + i] >> 8;
18162306a36Sopenharmony_ci			if (put_user(red, &ured[i]) ||
18262306a36Sopenharmony_ci			    put_user(green, &ugreen[i]) ||
18362306a36Sopenharmony_ci			    put_user(blue, &ublue[i]))
18462306a36Sopenharmony_ci				return -EFAULT;
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci		return 0;
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci	default:
18962306a36Sopenharmony_ci		return -EINVAL;
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ciEXPORT_SYMBOL(sbusfb_ioctl_helper);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
19562306a36Sopenharmony_ciint sbusfb_compat_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	switch (cmd) {
19862306a36Sopenharmony_ci	case FBIOGTYPE:
19962306a36Sopenharmony_ci	case FBIOSATTR:
20062306a36Sopenharmony_ci	case FBIOGATTR:
20162306a36Sopenharmony_ci	case FBIOSVIDEO:
20262306a36Sopenharmony_ci	case FBIOGVIDEO:
20362306a36Sopenharmony_ci	case FBIOSCURSOR32:
20462306a36Sopenharmony_ci	case FBIOGCURSOR32:	/* This is not implemented yet.
20562306a36Sopenharmony_ci				   Later it should be converted... */
20662306a36Sopenharmony_ci	case FBIOSCURPOS:
20762306a36Sopenharmony_ci	case FBIOGCURPOS:
20862306a36Sopenharmony_ci	case FBIOGCURMAX:
20962306a36Sopenharmony_ci		return info->fbops->fb_ioctl(info, cmd, arg);
21062306a36Sopenharmony_ci	case FBIOPUTCMAP32:
21162306a36Sopenharmony_ci	case FBIOPUTCMAP_SPARC: {
21262306a36Sopenharmony_ci		struct fbcmap32 c;
21362306a36Sopenharmony_ci		struct fb_cmap cmap;
21462306a36Sopenharmony_ci		u16 red, green, blue;
21562306a36Sopenharmony_ci		u8 red8, green8, blue8;
21662306a36Sopenharmony_ci		unsigned char __user *ured;
21762306a36Sopenharmony_ci		unsigned char __user *ugreen;
21862306a36Sopenharmony_ci		unsigned char __user *ublue;
21962306a36Sopenharmony_ci		unsigned int i;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		if (copy_from_user(&c, compat_ptr(arg), sizeof(c)))
22262306a36Sopenharmony_ci			return -EFAULT;
22362306a36Sopenharmony_ci		ured = compat_ptr(c.red);
22462306a36Sopenharmony_ci		ugreen = compat_ptr(c.green);
22562306a36Sopenharmony_ci		ublue = compat_ptr(c.blue);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci		cmap.len = 1;
22862306a36Sopenharmony_ci		cmap.red = &red;
22962306a36Sopenharmony_ci		cmap.green = &green;
23062306a36Sopenharmony_ci		cmap.blue = &blue;
23162306a36Sopenharmony_ci		cmap.transp = NULL;
23262306a36Sopenharmony_ci		for (i = 0; i < c.count; i++) {
23362306a36Sopenharmony_ci			int err;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci			if (get_user(red8, &ured[i]) ||
23662306a36Sopenharmony_ci			    get_user(green8, &ugreen[i]) ||
23762306a36Sopenharmony_ci			    get_user(blue8, &ublue[i]))
23862306a36Sopenharmony_ci				return -EFAULT;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci			red = red8 << 8;
24162306a36Sopenharmony_ci			green = green8 << 8;
24262306a36Sopenharmony_ci			blue = blue8 << 8;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci			cmap.start = c.index + i;
24562306a36Sopenharmony_ci			err = fb_set_cmap(&cmap, info);
24662306a36Sopenharmony_ci			if (err)
24762306a36Sopenharmony_ci				return err;
24862306a36Sopenharmony_ci		}
24962306a36Sopenharmony_ci		return 0;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci	case FBIOGETCMAP32: {
25262306a36Sopenharmony_ci		struct fbcmap32 c;
25362306a36Sopenharmony_ci		unsigned char __user *ured;
25462306a36Sopenharmony_ci		unsigned char __user *ugreen;
25562306a36Sopenharmony_ci		unsigned char __user *ublue;
25662306a36Sopenharmony_ci		struct fb_cmap *cmap = &info->cmap;
25762306a36Sopenharmony_ci		unsigned int index, i;
25862306a36Sopenharmony_ci		u8 red, green, blue;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		if (copy_from_user(&c, compat_ptr(arg), sizeof(c)))
26162306a36Sopenharmony_ci			return -EFAULT;
26262306a36Sopenharmony_ci		index = c.index;
26362306a36Sopenharmony_ci		ured = compat_ptr(c.red);
26462306a36Sopenharmony_ci		ugreen = compat_ptr(c.green);
26562306a36Sopenharmony_ci		ublue = compat_ptr(c.blue);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		if (index > cmap->len || c.count > cmap->len - index)
26862306a36Sopenharmony_ci			return -EINVAL;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci		for (i = 0; i < c.count; i++) {
27162306a36Sopenharmony_ci			red = cmap->red[index + i] >> 8;
27262306a36Sopenharmony_ci			green = cmap->green[index + i] >> 8;
27362306a36Sopenharmony_ci			blue = cmap->blue[index + i] >> 8;
27462306a36Sopenharmony_ci			if (put_user(red, &ured[i]) ||
27562306a36Sopenharmony_ci			    put_user(green, &ugreen[i]) ||
27662306a36Sopenharmony_ci			    put_user(blue, &ublue[i]))
27762306a36Sopenharmony_ci				return -EFAULT;
27862306a36Sopenharmony_ci		}
27962306a36Sopenharmony_ci		return 0;
28062306a36Sopenharmony_ci	}
28162306a36Sopenharmony_ci	default:
28262306a36Sopenharmony_ci		return -ENOIOCTLCMD;
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ciEXPORT_SYMBOL(sbusfb_compat_ioctl);
28662306a36Sopenharmony_ci#endif
287