xref: /linux/drivers/char/pc8736x_gpio.c (revision cb787f4ac0c2e439ea8d7e6387b925f74576bdf8)
1*09c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2681a3e7dSJim Cromie /* linux/drivers/char/pc8736x_gpio.c
3681a3e7dSJim Cromie 
4681a3e7dSJim Cromie    National Semiconductor PC8736x GPIO driver.  Allows a user space
5681a3e7dSJim Cromie    process to play with the GPIO pins.
6681a3e7dSJim Cromie 
727385085SJim Cromie    Copyright (c) 2005,2006 Jim Cromie <jim.cromie@gmail.com>
8681a3e7dSJim Cromie 
9681a3e7dSJim Cromie    adapted from linux/drivers/char/scx200_gpio.c
10681a3e7dSJim Cromie    Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>,
11681a3e7dSJim Cromie */
12681a3e7dSJim Cromie 
13681a3e7dSJim Cromie #include <linux/fs.h>
14681a3e7dSJim Cromie #include <linux/module.h>
15681a3e7dSJim Cromie #include <linux/errno.h>
16681a3e7dSJim Cromie #include <linux/kernel.h>
17681a3e7dSJim Cromie #include <linux/init.h>
1827385085SJim Cromie #include <linux/cdev.h>
19ec312310SJim Cromie #include <linux/io.h>
20681a3e7dSJim Cromie #include <linux/ioport.h>
218bcf6135SJim Cromie #include <linux/mutex.h>
22681a3e7dSJim Cromie #include <linux/nsc_gpio.h>
2358b087cdSJim Cromie #include <linux/platform_device.h>
247c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
25681a3e7dSJim Cromie 
2658b087cdSJim Cromie #define DEVNAME "pc8736x_gpio"
27681a3e7dSJim Cromie 
28681a3e7dSJim Cromie MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>");
294f197842SJim Cromie MODULE_DESCRIPTION("NatSemi/Winbond PC-8736x GPIO Pin Driver");
30681a3e7dSJim Cromie MODULE_LICENSE("GPL");
31681a3e7dSJim Cromie 
32681a3e7dSJim Cromie static int major;		/* default to dynamic major */
33681a3e7dSJim Cromie module_param(major, int, 0);
34681a3e7dSJim Cromie MODULE_PARM_DESC(major, "Major device number");
35681a3e7dSJim Cromie 
368bcf6135SJim Cromie static DEFINE_MUTEX(pc8736x_gpio_config_lock);
37681a3e7dSJim Cromie static unsigned pc8736x_gpio_base;
386cad56fdSJim Cromie static u8 pc8736x_gpio_shadow[4];
39681a3e7dSJim Cromie 
40681a3e7dSJim Cromie #define SIO_BASE1       0x2E	/* 1st command-reg to check */
41681a3e7dSJim Cromie #define SIO_BASE2       0x4E	/* alt command-reg to check */
42681a3e7dSJim Cromie 
43681a3e7dSJim Cromie #define SIO_SID		0x20	/* SuperI/O ID Register */
44b64fd291SAndre Haupt #define SIO_SID_PC87365	0xe5	/* Expected value in ID Register for PC87365 */
45b64fd291SAndre Haupt #define SIO_SID_PC87366	0xe9	/* Expected value in ID Register for PC87366 */
46681a3e7dSJim Cromie 
47681a3e7dSJim Cromie #define SIO_CF1		0x21	/* chip config, bit0 is chip enable */
48681a3e7dSJim Cromie 
494f197842SJim Cromie #define PC8736X_GPIO_RANGE	16 /* ioaddr range */
504f197842SJim Cromie #define PC8736X_GPIO_CT		32 /* minors matching 4 8 bit ports */
5158b087cdSJim Cromie 
52681a3e7dSJim Cromie #define SIO_UNIT_SEL	0x7	/* unit select reg */
53681a3e7dSJim Cromie #define SIO_UNIT_ACT	0x30	/* unit enable */
54681a3e7dSJim Cromie #define SIO_GPIO_UNIT	0x7	/* unit number of GPIO */
55681a3e7dSJim Cromie #define SIO_VLM_UNIT	0x0D
56681a3e7dSJim Cromie #define SIO_TMS_UNIT	0x0E
57681a3e7dSJim Cromie 
58681a3e7dSJim Cromie /* config-space addrs to read/write each unit's runtime addr */
59681a3e7dSJim Cromie #define SIO_BASE_HADDR		0x60
60681a3e7dSJim Cromie #define SIO_BASE_LADDR		0x61
61681a3e7dSJim Cromie 
62681a3e7dSJim Cromie /* GPIO config-space pin-control addresses */
63681a3e7dSJim Cromie #define SIO_GPIO_PIN_SELECT	0xF0
64681a3e7dSJim Cromie #define SIO_GPIO_PIN_CONFIG     0xF1
65681a3e7dSJim Cromie #define SIO_GPIO_PIN_EVENT      0xF2
66681a3e7dSJim Cromie 
67681a3e7dSJim Cromie static unsigned char superio_cmd = 0;
68681a3e7dSJim Cromie static unsigned char selected_device = 0xFF;	/* bogus start val */
69681a3e7dSJim Cromie 
70681a3e7dSJim Cromie /* GPIO port runtime access, functionality */
71681a3e7dSJim Cromie static int port_offset[] = { 0, 4, 8, 10 };	/* non-uniform offsets ! */
72681a3e7dSJim Cromie /* static int event_capable[] = { 1, 1, 0, 0 };   ports 2,3 are hobbled */
73681a3e7dSJim Cromie 
74681a3e7dSJim Cromie #define PORT_OUT	0
75681a3e7dSJim Cromie #define PORT_IN		1
76681a3e7dSJim Cromie #define PORT_EVT_EN	2
77681a3e7dSJim Cromie #define PORT_EVT_STST	3
78681a3e7dSJim Cromie 
7958b087cdSJim Cromie static struct platform_device *pdev;  /* use in dev_*() */
8058b087cdSJim Cromie 
81681a3e7dSJim Cromie static inline void superio_outb(int addr, int val)
82681a3e7dSJim Cromie {
83681a3e7dSJim Cromie 	outb_p(addr, superio_cmd);
84681a3e7dSJim Cromie 	outb_p(val, superio_cmd + 1);
85681a3e7dSJim Cromie }
86681a3e7dSJim Cromie 
87681a3e7dSJim Cromie static inline int superio_inb(int addr)
88681a3e7dSJim Cromie {
89681a3e7dSJim Cromie 	outb_p(addr, superio_cmd);
90681a3e7dSJim Cromie 	return inb_p(superio_cmd + 1);
91681a3e7dSJim Cromie }
92681a3e7dSJim Cromie 
93681a3e7dSJim Cromie static int pc8736x_superio_present(void)
94681a3e7dSJim Cromie {
95b64fd291SAndre Haupt 	int id;
96b64fd291SAndre Haupt 
97681a3e7dSJim Cromie 	/* try the 2 possible values, read a hardware reg to verify */
98681a3e7dSJim Cromie 	superio_cmd = SIO_BASE1;
99b64fd291SAndre Haupt 	id = superio_inb(SIO_SID);
100b64fd291SAndre Haupt 	if (id == SIO_SID_PC87365 || id == SIO_SID_PC87366)
101681a3e7dSJim Cromie 		return superio_cmd;
102681a3e7dSJim Cromie 
103681a3e7dSJim Cromie 	superio_cmd = SIO_BASE2;
104b64fd291SAndre Haupt 	id = superio_inb(SIO_SID);
105b64fd291SAndre Haupt 	if (id == SIO_SID_PC87365 || id == SIO_SID_PC87366)
106681a3e7dSJim Cromie 		return superio_cmd;
107681a3e7dSJim Cromie 
108681a3e7dSJim Cromie 	return 0;
109681a3e7dSJim Cromie }
110681a3e7dSJim Cromie 
111681a3e7dSJim Cromie static void device_select(unsigned devldn)
112681a3e7dSJim Cromie {
113681a3e7dSJim Cromie 	superio_outb(SIO_UNIT_SEL, devldn);
114681a3e7dSJim Cromie 	selected_device = devldn;
115681a3e7dSJim Cromie }
116681a3e7dSJim Cromie 
117681a3e7dSJim Cromie static void select_pin(unsigned iminor)
118681a3e7dSJim Cromie {
119681a3e7dSJim Cromie 	/* select GPIO port/pin from device minor number */
120681a3e7dSJim Cromie 	device_select(SIO_GPIO_UNIT);
121681a3e7dSJim Cromie 	superio_outb(SIO_GPIO_PIN_SELECT,
122681a3e7dSJim Cromie 		     ((iminor << 1) & 0xF0) | (iminor & 0x7));
123681a3e7dSJim Cromie }
124681a3e7dSJim Cromie 
125681a3e7dSJim Cromie static inline u32 pc8736x_gpio_configure_fn(unsigned index, u32 mask, u32 bits,
126681a3e7dSJim Cromie 					    u32 func_slct)
127681a3e7dSJim Cromie {
128681a3e7dSJim Cromie 	u32 config, new_config;
129681a3e7dSJim Cromie 
1308bcf6135SJim Cromie 	mutex_lock(&pc8736x_gpio_config_lock);
131681a3e7dSJim Cromie 
132681a3e7dSJim Cromie 	device_select(SIO_GPIO_UNIT);
133681a3e7dSJim Cromie 	select_pin(index);
134681a3e7dSJim Cromie 
135681a3e7dSJim Cromie 	/* read current config value */
136681a3e7dSJim Cromie 	config = superio_inb(func_slct);
137681a3e7dSJim Cromie 
138681a3e7dSJim Cromie 	/* set new config */
139681a3e7dSJim Cromie 	new_config = (config & mask) | bits;
140681a3e7dSJim Cromie 	superio_outb(func_slct, new_config);
141681a3e7dSJim Cromie 
1428bcf6135SJim Cromie 	mutex_unlock(&pc8736x_gpio_config_lock);
143681a3e7dSJim Cromie 
144681a3e7dSJim Cromie 	return config;
145681a3e7dSJim Cromie }
146681a3e7dSJim Cromie 
147681a3e7dSJim Cromie static u32 pc8736x_gpio_configure(unsigned index, u32 mask, u32 bits)
148681a3e7dSJim Cromie {
149681a3e7dSJim Cromie 	return pc8736x_gpio_configure_fn(index, mask, bits,
150681a3e7dSJim Cromie 					 SIO_GPIO_PIN_CONFIG);
151681a3e7dSJim Cromie }
152681a3e7dSJim Cromie 
153681a3e7dSJim Cromie static int pc8736x_gpio_get(unsigned minor)
154681a3e7dSJim Cromie {
155681a3e7dSJim Cromie 	int port, bit, val;
156681a3e7dSJim Cromie 
157681a3e7dSJim Cromie 	port = minor >> 3;
158681a3e7dSJim Cromie 	bit = minor & 7;
159681a3e7dSJim Cromie 	val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);
160681a3e7dSJim Cromie 	val >>= bit;
161681a3e7dSJim Cromie 	val &= 1;
162681a3e7dSJim Cromie 
16358b087cdSJim Cromie 	dev_dbg(&pdev->dev, "_gpio_get(%d from %x bit %d) == val %d\n",
164681a3e7dSJim Cromie 		minor, pc8736x_gpio_base + port_offset[port] + PORT_IN, bit,
165681a3e7dSJim Cromie 		val);
166681a3e7dSJim Cromie 
167681a3e7dSJim Cromie 	return val;
168681a3e7dSJim Cromie }
169681a3e7dSJim Cromie 
170681a3e7dSJim Cromie static void pc8736x_gpio_set(unsigned minor, int val)
171681a3e7dSJim Cromie {
172681a3e7dSJim Cromie 	int port, bit, curval;
173681a3e7dSJim Cromie 
174681a3e7dSJim Cromie 	minor &= 0x1f;
175681a3e7dSJim Cromie 	port = minor >> 3;
176681a3e7dSJim Cromie 	bit = minor & 7;
177681a3e7dSJim Cromie 	curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);
178681a3e7dSJim Cromie 
17958b087cdSJim Cromie 	dev_dbg(&pdev->dev, "addr:%x cur:%x bit-pos:%d cur-bit:%x + new:%d -> bit-new:%d\n",
180681a3e7dSJim Cromie 		pc8736x_gpio_base + port_offset[port] + PORT_OUT,
181681a3e7dSJim Cromie 		curval, bit, (curval & ~(1 << bit)), val, (val << bit));
182681a3e7dSJim Cromie 
183681a3e7dSJim Cromie 	val = (curval & ~(1 << bit)) | (val << bit);
184681a3e7dSJim Cromie 
18558b087cdSJim Cromie 	dev_dbg(&pdev->dev, "gpio_set(minor:%d port:%d bit:%d)"
18658b087cdSJim Cromie 		" %2x -> %2x\n", minor, port, bit, curval, val);
187681a3e7dSJim Cromie 
188681a3e7dSJim Cromie 	outb_p(val, pc8736x_gpio_base + port_offset[port] + PORT_OUT);
189681a3e7dSJim Cromie 
190681a3e7dSJim Cromie 	curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);
191681a3e7dSJim Cromie 	val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);
192681a3e7dSJim Cromie 
19358b087cdSJim Cromie 	dev_dbg(&pdev->dev, "wrote %x, read: %x\n", curval, val);
1946cad56fdSJim Cromie 	pc8736x_gpio_shadow[port] = val;
195681a3e7dSJim Cromie }
196681a3e7dSJim Cromie 
1976cad56fdSJim Cromie static int pc8736x_gpio_current(unsigned minor)
198681a3e7dSJim Cromie {
1996cad56fdSJim Cromie 	int port, bit;
2006cad56fdSJim Cromie 	minor &= 0x1f;
2016cad56fdSJim Cromie 	port = minor >> 3;
2026cad56fdSJim Cromie 	bit = minor & 7;
2036cad56fdSJim Cromie 	return ((pc8736x_gpio_shadow[port] >> bit) & 0x01);
204681a3e7dSJim Cromie }
205681a3e7dSJim Cromie 
206681a3e7dSJim Cromie static void pc8736x_gpio_change(unsigned index)
207681a3e7dSJim Cromie {
2086cad56fdSJim Cromie 	pc8736x_gpio_set(index, !pc8736x_gpio_current(index));
209681a3e7dSJim Cromie }
210681a3e7dSJim Cromie 
2112e8f7a31SJim Cromie static struct nsc_gpio_ops pc8736x_gpio_ops = {
212681a3e7dSJim Cromie 	.owner		= THIS_MODULE,
213681a3e7dSJim Cromie 	.gpio_config	= pc8736x_gpio_configure,
214681a3e7dSJim Cromie 	.gpio_dump	= nsc_gpio_dump,
215681a3e7dSJim Cromie 	.gpio_get	= pc8736x_gpio_get,
216681a3e7dSJim Cromie 	.gpio_set	= pc8736x_gpio_set,
217681a3e7dSJim Cromie 	.gpio_change	= pc8736x_gpio_change,
218681a3e7dSJim Cromie 	.gpio_current	= pc8736x_gpio_current
219681a3e7dSJim Cromie };
220681a3e7dSJim Cromie 
221681a3e7dSJim Cromie static int pc8736x_gpio_open(struct inode *inode, struct file *file)
222681a3e7dSJim Cromie {
223681a3e7dSJim Cromie 	unsigned m = iminor(inode);
2242e8f7a31SJim Cromie 	file->private_data = &pc8736x_gpio_ops;
225681a3e7dSJim Cromie 
22658b087cdSJim Cromie 	dev_dbg(&pdev->dev, "open %d\n", m);
227681a3e7dSJim Cromie 
2284f197842SJim Cromie 	if (m >= PC8736X_GPIO_CT)
229681a3e7dSJim Cromie 		return -EINVAL;
230681a3e7dSJim Cromie 	return nonseekable_open(inode, file);
231681a3e7dSJim Cromie }
232681a3e7dSJim Cromie 
2332e8f7a31SJim Cromie static const struct file_operations pc8736x_gpio_fileops = {
234681a3e7dSJim Cromie 	.owner	= THIS_MODULE,
235681a3e7dSJim Cromie 	.open	= pc8736x_gpio_open,
236681a3e7dSJim Cromie 	.write	= nsc_gpio_write,
237681a3e7dSJim Cromie 	.read	= nsc_gpio_read,
238681a3e7dSJim Cromie };
239681a3e7dSJim Cromie 
2406cad56fdSJim Cromie static void __init pc8736x_init_shadow(void)
2416cad56fdSJim Cromie {
2426cad56fdSJim Cromie 	int port;
2436cad56fdSJim Cromie 
2446cad56fdSJim Cromie 	/* read the current values driven on the GPIO signals */
2456cad56fdSJim Cromie 	for (port = 0; port < 4; ++port)
2466cad56fdSJim Cromie 		pc8736x_gpio_shadow[port]
2476cad56fdSJim Cromie 		    = inb_p(pc8736x_gpio_base + port_offset[port]
2486cad56fdSJim Cromie 			    + PORT_OUT);
2496cad56fdSJim Cromie 
2506cad56fdSJim Cromie }
2516cad56fdSJim Cromie 
25227385085SJim Cromie static struct cdev pc8736x_gpio_cdev;
25327385085SJim Cromie 
254681a3e7dSJim Cromie static int __init pc8736x_gpio_init(void)
255681a3e7dSJim Cromie {
256babcfadeSJim Cromie 	int rc;
257babcfadeSJim Cromie 	dev_t devid;
258681a3e7dSJim Cromie 
25958b087cdSJim Cromie 	pdev = platform_device_alloc(DEVNAME, 0);
26058b087cdSJim Cromie 	if (!pdev)
26158b087cdSJim Cromie 		return -ENOMEM;
26258b087cdSJim Cromie 
26358b087cdSJim Cromie 	rc = platform_device_add(pdev);
26458b087cdSJim Cromie 	if (rc) {
26558b087cdSJim Cromie 		rc = -ENODEV;
26658b087cdSJim Cromie 		goto undo_platform_dev_alloc;
26758b087cdSJim Cromie 	}
26858b087cdSJim Cromie 	dev_info(&pdev->dev, "NatSemi pc8736x GPIO Driver Initializing\n");
269681a3e7dSJim Cromie 
270681a3e7dSJim Cromie 	if (!pc8736x_superio_present()) {
27158b087cdSJim Cromie 		rc = -ENODEV;
27258b087cdSJim Cromie 		dev_err(&pdev->dev, "no device found\n");
27358b087cdSJim Cromie 		goto undo_platform_dev_add;
274681a3e7dSJim Cromie 	}
2752e8f7a31SJim Cromie 	pc8736x_gpio_ops.dev = &pdev->dev;
276681a3e7dSJim Cromie 
277681a3e7dSJim Cromie 	/* Verify that chip and it's GPIO unit are both enabled.
278681a3e7dSJim Cromie 	   My BIOS does this, so I take minimum action here
279681a3e7dSJim Cromie 	 */
280681a3e7dSJim Cromie 	rc = superio_inb(SIO_CF1);
281681a3e7dSJim Cromie 	if (!(rc & 0x01)) {
28258b087cdSJim Cromie 		rc = -ENODEV;
28358b087cdSJim Cromie 		dev_err(&pdev->dev, "device not enabled\n");
28458b087cdSJim Cromie 		goto undo_platform_dev_add;
285681a3e7dSJim Cromie 	}
286681a3e7dSJim Cromie 	device_select(SIO_GPIO_UNIT);
287681a3e7dSJim Cromie 	if (!superio_inb(SIO_UNIT_ACT)) {
28858b087cdSJim Cromie 		rc = -ENODEV;
28958b087cdSJim Cromie 		dev_err(&pdev->dev, "GPIO unit not enabled\n");
29058b087cdSJim Cromie 		goto undo_platform_dev_add;
291681a3e7dSJim Cromie 	}
292681a3e7dSJim Cromie 
29358b087cdSJim Cromie 	/* read the GPIO unit base addr that chip responds to */
294681a3e7dSJim Cromie 	pc8736x_gpio_base = (superio_inb(SIO_BASE_HADDR) << 8
295681a3e7dSJim Cromie 			     | superio_inb(SIO_BASE_LADDR));
296681a3e7dSJim Cromie 
2974f197842SJim Cromie 	if (!request_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE, DEVNAME)) {
29858b087cdSJim Cromie 		rc = -ENODEV;
29958b087cdSJim Cromie 		dev_err(&pdev->dev, "GPIO ioport %x busy\n",
300681a3e7dSJim Cromie 			pc8736x_gpio_base);
30158b087cdSJim Cromie 		goto undo_platform_dev_add;
30258b087cdSJim Cromie 	}
30358b087cdSJim Cromie 	dev_info(&pdev->dev, "GPIO ioport %x reserved\n", pc8736x_gpio_base);
304681a3e7dSJim Cromie 
305babcfadeSJim Cromie 	if (major) {
306babcfadeSJim Cromie 		devid = MKDEV(major, 0);
307babcfadeSJim Cromie 		rc = register_chrdev_region(devid, PC8736X_GPIO_CT, DEVNAME);
308babcfadeSJim Cromie 	} else {
309babcfadeSJim Cromie 		rc = alloc_chrdev_region(&devid, 0, PC8736X_GPIO_CT, DEVNAME);
310babcfadeSJim Cromie 		major = MAJOR(devid);
311babcfadeSJim Cromie 	}
312babcfadeSJim Cromie 
31358b087cdSJim Cromie 	if (rc < 0) {
31458b087cdSJim Cromie 		dev_err(&pdev->dev, "register-chrdev failed: %d\n", rc);
31527385085SJim Cromie 		goto undo_request_region;
316681a3e7dSJim Cromie 	}
317681a3e7dSJim Cromie 	if (!major) {
31858b087cdSJim Cromie 		major = rc;
31958b087cdSJim Cromie 		dev_dbg(&pdev->dev, "got dynamic major %d\n", major);
320681a3e7dSJim Cromie 	}
321681a3e7dSJim Cromie 
322681a3e7dSJim Cromie 	pc8736x_init_shadow();
323babcfadeSJim Cromie 
324babcfadeSJim Cromie 	/* ignore minor errs, and succeed */
3252e8f7a31SJim Cromie 	cdev_init(&pc8736x_gpio_cdev, &pc8736x_gpio_fileops);
326babcfadeSJim Cromie 	cdev_add(&pc8736x_gpio_cdev, devid, PC8736X_GPIO_CT);
327babcfadeSJim Cromie 
328681a3e7dSJim Cromie 	return 0;
32958b087cdSJim Cromie 
33027385085SJim Cromie undo_request_region:
33127385085SJim Cromie 	release_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE);
33258b087cdSJim Cromie undo_platform_dev_add:
3331017f6afSIngo Molnar 	platform_device_del(pdev);
33458b087cdSJim Cromie undo_platform_dev_alloc:
3351017f6afSIngo Molnar 	platform_device_put(pdev);
3361017f6afSIngo Molnar 
33758b087cdSJim Cromie 	return rc;
338681a3e7dSJim Cromie }
339681a3e7dSJim Cromie 
340681a3e7dSJim Cromie static void __exit pc8736x_gpio_cleanup(void)
341681a3e7dSJim Cromie {
34258b087cdSJim Cromie 	dev_dbg(&pdev->dev, "cleanup\n");
343681a3e7dSJim Cromie 
34427385085SJim Cromie 	cdev_del(&pc8736x_gpio_cdev);
34527385085SJim Cromie 	unregister_chrdev_region(MKDEV(major,0), PC8736X_GPIO_CT);
34627385085SJim Cromie 	release_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE);
347681a3e7dSJim Cromie 
3481b3c1655SWei Yongjun 	platform_device_unregister(pdev);
349681a3e7dSJim Cromie }
350681a3e7dSJim Cromie 
351681a3e7dSJim Cromie module_init(pc8736x_gpio_init);
352681a3e7dSJim Cromie module_exit(pc8736x_gpio_cleanup);
353