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