brintos

brintos / linux-shallow public Read only

0
0
Text · 8.8 KiB · 5f46968 Raw
353 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* linux/drivers/char/pc8736x_gpio.c3 4   National Semiconductor PC8736x GPIO driver.  Allows a user space5   process to play with the GPIO pins.6 7   Copyright (c) 2005,2006 Jim Cromie <jim.cromie@gmail.com>8 9   adapted from linux/drivers/char/scx200_gpio.c10   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>,11*/12 13#include <linux/fs.h>14#include <linux/module.h>15#include <linux/errno.h>16#include <linux/kernel.h>17#include <linux/init.h>18#include <linux/cdev.h>19#include <linux/io.h>20#include <linux/ioport.h>21#include <linux/mutex.h>22#include <linux/nsc_gpio.h>23#include <linux/platform_device.h>24#include <linux/uaccess.h>25 26#define DEVNAME "pc8736x_gpio"27 28MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>");29MODULE_DESCRIPTION("NatSemi/Winbond PC-8736x GPIO Pin Driver");30MODULE_LICENSE("GPL");31 32static int major;		/* default to dynamic major */33module_param(major, int, 0);34MODULE_PARM_DESC(major, "Major device number");35 36static DEFINE_MUTEX(pc8736x_gpio_config_lock);37static unsigned pc8736x_gpio_base;38static u8 pc8736x_gpio_shadow[4];39 40#define SIO_BASE1       0x2E	/* 1st command-reg to check */41#define SIO_BASE2       0x4E	/* alt command-reg to check */42 43#define SIO_SID		0x20	/* SuperI/O ID Register */44#define SIO_SID_PC87365	0xe5	/* Expected value in ID Register for PC87365 */45#define SIO_SID_PC87366	0xe9	/* Expected value in ID Register for PC87366 */46 47#define SIO_CF1		0x21	/* chip config, bit0 is chip enable */48 49#define PC8736X_GPIO_RANGE	16 /* ioaddr range */50#define PC8736X_GPIO_CT		32 /* minors matching 4 8 bit ports */51 52#define SIO_UNIT_SEL	0x7	/* unit select reg */53#define SIO_UNIT_ACT	0x30	/* unit enable */54#define SIO_GPIO_UNIT	0x7	/* unit number of GPIO */55#define SIO_VLM_UNIT	0x0D56#define SIO_TMS_UNIT	0x0E57 58/* config-space addrs to read/write each unit's runtime addr */59#define SIO_BASE_HADDR		0x6060#define SIO_BASE_LADDR		0x6161 62/* GPIO config-space pin-control addresses */63#define SIO_GPIO_PIN_SELECT	0xF064#define SIO_GPIO_PIN_CONFIG     0xF165#define SIO_GPIO_PIN_EVENT      0xF266 67static unsigned char superio_cmd = 0;68static unsigned char selected_device = 0xFF;	/* bogus start val */69 70/* GPIO port runtime access, functionality */71static int port_offset[] = { 0, 4, 8, 10 };	/* non-uniform offsets ! */72/* static int event_capable[] = { 1, 1, 0, 0 };   ports 2,3 are hobbled */73 74#define PORT_OUT	075#define PORT_IN		176#define PORT_EVT_EN	277#define PORT_EVT_STST	378 79static struct platform_device *pdev;  /* use in dev_*() */80 81static inline void superio_outb(int addr, int val)82{83	outb_p(addr, superio_cmd);84	outb_p(val, superio_cmd + 1);85}86 87static inline int superio_inb(int addr)88{89	outb_p(addr, superio_cmd);90	return inb_p(superio_cmd + 1);91}92 93static int pc8736x_superio_present(void)94{95	int id;96 97	/* try the 2 possible values, read a hardware reg to verify */98	superio_cmd = SIO_BASE1;99	id = superio_inb(SIO_SID);100	if (id == SIO_SID_PC87365 || id == SIO_SID_PC87366)101		return superio_cmd;102 103	superio_cmd = SIO_BASE2;104	id = superio_inb(SIO_SID);105	if (id == SIO_SID_PC87365 || id == SIO_SID_PC87366)106		return superio_cmd;107 108	return 0;109}110 111static void device_select(unsigned devldn)112{113	superio_outb(SIO_UNIT_SEL, devldn);114	selected_device = devldn;115}116 117static void select_pin(unsigned iminor)118{119	/* select GPIO port/pin from device minor number */120	device_select(SIO_GPIO_UNIT);121	superio_outb(SIO_GPIO_PIN_SELECT,122		     ((iminor << 1) & 0xF0) | (iminor & 0x7));123}124 125static inline u32 pc8736x_gpio_configure_fn(unsigned index, u32 mask, u32 bits,126					    u32 func_slct)127{128	u32 config, new_config;129 130	mutex_lock(&pc8736x_gpio_config_lock);131 132	device_select(SIO_GPIO_UNIT);133	select_pin(index);134 135	/* read current config value */136	config = superio_inb(func_slct);137 138	/* set new config */139	new_config = (config & mask) | bits;140	superio_outb(func_slct, new_config);141 142	mutex_unlock(&pc8736x_gpio_config_lock);143 144	return config;145}146 147static u32 pc8736x_gpio_configure(unsigned index, u32 mask, u32 bits)148{149	return pc8736x_gpio_configure_fn(index, mask, bits,150					 SIO_GPIO_PIN_CONFIG);151}152 153static int pc8736x_gpio_get(unsigned minor)154{155	int port, bit, val;156 157	port = minor >> 3;158	bit = minor & 7;159	val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);160	val >>= bit;161	val &= 1;162 163	dev_dbg(&pdev->dev, "_gpio_get(%d from %x bit %d) == val %d\n",164		minor, pc8736x_gpio_base + port_offset[port] + PORT_IN, bit,165		val);166 167	return val;168}169 170static void pc8736x_gpio_set(unsigned minor, int val)171{172	int port, bit, curval;173 174	minor &= 0x1f;175	port = minor >> 3;176	bit = minor & 7;177	curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);178 179	dev_dbg(&pdev->dev, "addr:%x cur:%x bit-pos:%d cur-bit:%x + new:%d -> bit-new:%d\n",180		pc8736x_gpio_base + port_offset[port] + PORT_OUT,181		curval, bit, (curval & ~(1 << bit)), val, (val << bit));182 183	val = (curval & ~(1 << bit)) | (val << bit);184 185	dev_dbg(&pdev->dev, "gpio_set(minor:%d port:%d bit:%d)"186		" %2x -> %2x\n", minor, port, bit, curval, val);187 188	outb_p(val, pc8736x_gpio_base + port_offset[port] + PORT_OUT);189 190	curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);191	val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);192 193	dev_dbg(&pdev->dev, "wrote %x, read: %x\n", curval, val);194	pc8736x_gpio_shadow[port] = val;195}196 197static int pc8736x_gpio_current(unsigned minor)198{199	int port, bit;200	minor &= 0x1f;201	port = minor >> 3;202	bit = minor & 7;203	return ((pc8736x_gpio_shadow[port] >> bit) & 0x01);204}205 206static void pc8736x_gpio_change(unsigned index)207{208	pc8736x_gpio_set(index, !pc8736x_gpio_current(index));209}210 211static struct nsc_gpio_ops pc8736x_gpio_ops = {212	.owner		= THIS_MODULE,213	.gpio_config	= pc8736x_gpio_configure,214	.gpio_dump	= nsc_gpio_dump,215	.gpio_get	= pc8736x_gpio_get,216	.gpio_set	= pc8736x_gpio_set,217	.gpio_change	= pc8736x_gpio_change,218	.gpio_current	= pc8736x_gpio_current219};220 221static int pc8736x_gpio_open(struct inode *inode, struct file *file)222{223	unsigned m = iminor(inode);224	file->private_data = &pc8736x_gpio_ops;225 226	dev_dbg(&pdev->dev, "open %d\n", m);227 228	if (m >= PC8736X_GPIO_CT)229		return -EINVAL;230	return nonseekable_open(inode, file);231}232 233static const struct file_operations pc8736x_gpio_fileops = {234	.owner	= THIS_MODULE,235	.open	= pc8736x_gpio_open,236	.write	= nsc_gpio_write,237	.read	= nsc_gpio_read,238};239 240static void __init pc8736x_init_shadow(void)241{242	int port;243 244	/* read the current values driven on the GPIO signals */245	for (port = 0; port < 4; ++port)246		pc8736x_gpio_shadow[port]247		    = inb_p(pc8736x_gpio_base + port_offset[port]248			    + PORT_OUT);249 250}251 252static struct cdev pc8736x_gpio_cdev;253 254static int __init pc8736x_gpio_init(void)255{256	int rc;257	dev_t devid;258 259	pdev = platform_device_alloc(DEVNAME, 0);260	if (!pdev)261		return -ENOMEM;262 263	rc = platform_device_add(pdev);264	if (rc) {265		rc = -ENODEV;266		goto undo_platform_dev_alloc;267	}268	dev_info(&pdev->dev, "NatSemi pc8736x GPIO Driver Initializing\n");269 270	if (!pc8736x_superio_present()) {271		rc = -ENODEV;272		dev_err(&pdev->dev, "no device found\n");273		goto undo_platform_dev_add;274	}275	pc8736x_gpio_ops.dev = &pdev->dev;276 277	/* Verify that chip and it's GPIO unit are both enabled.278	   My BIOS does this, so I take minimum action here279	 */280	rc = superio_inb(SIO_CF1);281	if (!(rc & 0x01)) {282		rc = -ENODEV;283		dev_err(&pdev->dev, "device not enabled\n");284		goto undo_platform_dev_add;285	}286	device_select(SIO_GPIO_UNIT);287	if (!superio_inb(SIO_UNIT_ACT)) {288		rc = -ENODEV;289		dev_err(&pdev->dev, "GPIO unit not enabled\n");290		goto undo_platform_dev_add;291	}292 293	/* read the GPIO unit base addr that chip responds to */294	pc8736x_gpio_base = (superio_inb(SIO_BASE_HADDR) << 8295			     | superio_inb(SIO_BASE_LADDR));296 297	if (!request_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE, DEVNAME)) {298		rc = -ENODEV;299		dev_err(&pdev->dev, "GPIO ioport %x busy\n",300			pc8736x_gpio_base);301		goto undo_platform_dev_add;302	}303	dev_info(&pdev->dev, "GPIO ioport %x reserved\n", pc8736x_gpio_base);304 305	if (major) {306		devid = MKDEV(major, 0);307		rc = register_chrdev_region(devid, PC8736X_GPIO_CT, DEVNAME);308	} else {309		rc = alloc_chrdev_region(&devid, 0, PC8736X_GPIO_CT, DEVNAME);310		major = MAJOR(devid);311	}312 313	if (rc < 0) {314		dev_err(&pdev->dev, "register-chrdev failed: %d\n", rc);315		goto undo_request_region;316	}317	if (!major) {318		major = rc;319		dev_dbg(&pdev->dev, "got dynamic major %d\n", major);320	}321 322	pc8736x_init_shadow();323 324	/* ignore minor errs, and succeed */325	cdev_init(&pc8736x_gpio_cdev, &pc8736x_gpio_fileops);326	cdev_add(&pc8736x_gpio_cdev, devid, PC8736X_GPIO_CT);327 328	return 0;329 330undo_request_region:331	release_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE);332undo_platform_dev_add:333	platform_device_del(pdev);334undo_platform_dev_alloc:335	platform_device_put(pdev);336 337	return rc;338}339 340static void __exit pc8736x_gpio_cleanup(void)341{342	dev_dbg(&pdev->dev, "cleanup\n");343 344	cdev_del(&pc8736x_gpio_cdev);345	unregister_chrdev_region(MKDEV(major,0), PC8736X_GPIO_CT);346	release_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE);347 348	platform_device_unregister(pdev);349}350 351module_init(pc8736x_gpio_init);352module_exit(pc8736x_gpio_cleanup);353