/* * This file is provided under a dual BSD/GPLv2 license. When using or * redistributing this file, you may do so under either license. * * GPL LICENSE SUMMARY * * Copyright(c) 2007,2008 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * The full GNU General Public License is included in this distribution * in the file called LICENSE.GPL. * * Contact Information: * Intel Corporation * * BSD LICENSE * * Copyright(c) 2007,2008 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * version: Embedded.L.0.7.187 */ /* * NetApp change history @ 21-Dec-2011 (under CONFIG_NETAPP_HWDD): * - renamed driver locally to hwdd_gpio.c * - var "DRIVERNAME" changed to "hwdd_gpio", from "gpio_ref" * - LPC_DEVICE_ID changed to 0x2918(from 0x5031) w.r.t ICH9 spec for Carnegie, * -- This should be changed(value awaited) w.r.t PCH Spec accordingly for BBA * - Enhancement this driver needs, should the GPIO signals goes beyond 64 * (-- now itself count is 76 - referred recent PCM doc dated 20-Dec-2011) * -- Need to add offsets for SEL3 and LVL3 as per PCH doc: * --- Offset 40h: GPIO_USE_SEL3 * --- Offset 44h: GP_IO_SEL3 * --- Offset 48h: GP_LVL3 * --- Offset 60h: GP_RST_SEL * -- Max gpio signals from 64 to 128 and thereby increase memory size for same from 64B to 128B * -- LPC device spec(Bus/Dev/Fn.) on PCI bus w.r.t. PCH spec. (eg: LPC_DEVICE_ID) * -- any other change to incorporate above comments or other! */ /* NetApp change history @ 20-Jul-2012: * added support for another 32 pins for PCH, now total of 96 pins * LVL3, SEL3 added for PCH's pins beyond 64 * macro PCH and ICH9 introduced */ /***************************************************************************** * @ingroup GPIO_GENERAL * * @file gpio_ref.c * * @description * The gpio module is a linux driver which provides a reference * implementation for the control of the gpio signals. * Communication is done through the ioctl API * and gpio signal control is accomplished using the set of ioctl * control codes which are defined for this driver. * *****************************************************************************/ #include "gpio.h" MODULE_AUTHOR("Intel(R) Corporation"); MODULE_DESCRIPTION("General Purpose I/O Pin Driver"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_VERSION("0.8.0"); drvr_data_t g_drvr_data; /***************************************************************************** Description: Entry point for the driver. Parameters: none Returns: 0 => success < 0 => error ******************************************************************************/ int gpio_init(void) { int ret; dev_t devno; struct pci_dev *pdev = NULL; g_drvr_data.mem_base = 0; g_drvr_data.mem_resrvd = 0; /* request and reserve a device number */ ret = alloc_chrdev_region(&g_drvr_data.devnum, 0, 1, DRIVERNAME); if ( ret < 0) { printk("%s:gpio_init-Could not register module\n", DRIVERNAME); return ret; } /* init cdev struct for adding device to kernel */ cdev_init(&g_drvr_data.cdev, &file_ops); g_drvr_data.cdev.owner = THIS_MODULE; g_drvr_data.cdev.ops = &file_ops; devno = MKDEV(MAJOR(g_drvr_data.devnum), 0); if ( cdev_add(&g_drvr_data.cdev, devno, 1) ) { printk("%s:gpio_init-cdev_add failed\n", DRIVERNAME); goto Exit_Error; } /* Get dev struct for the LPC device. The GPIO BAR is located in the LPC device config space */ pdev = pci_get_device(LPC_VENDOR_ID, LPC_DEVICE_ID, NULL); if ( !pdev ) { printk("%s:gpio_init-Could not find pci device\n", DRIVERNAME); goto Exit_Error; } /* Get base address from the LPC configuration space. */ pci_read_config_dword(pdev, GPIO_BAR_OFFSET, &(g_drvr_data.mem_base)); /* remove the lsb which indicates io memory */ g_drvr_data.mem_base -= 1; printk("base addr %x\n", g_drvr_data.mem_base); /* release reference to device */ pci_dev_put(pdev); /* obtain exclusive access to GPIO memory space */ if ( !request_region(g_drvr_data.mem_base, GPIO_MEM_SIZE, DRIVERNAME) ) { printk("%s:gpio_init-IO memory region cannot be accessed\n", DRIVERNAME); g_drvr_data.mem_resrvd = 0; //NTAP: seems memory beyond 64B claimed by dummy peripheral, take it, dont err! //goto Exit_Error; } else { /* indicate memory space reserved */ g_drvr_data.mem_resrvd = 1; } /* set the addrs in the i/o memory addr structure */ set_reg_addrs(g_drvr_data.mem_base); goto Exit; Exit_Error: printk("%s:gpio_init-Initialization failed\n", DRIVERNAME); gpio_close(); return -ENODEV; Exit: printk("%s:gpio_init-Initialization complete\n", DRIVERNAME); return 0; } /***************************************************************************** Description: This function is called when the driver is unloaded from memory. Parameters: none Returns: none ******************************************************************************/ void gpio_close(void) { /* remove cdev struct from system */ cdev_del(&g_drvr_data.cdev); /* unregister driver module */ unregister_chrdev_region(g_drvr_data.devnum, 1); /* release the reserved IO memory space */ if ( g_drvr_data.mem_resrvd ) { release_region(g_drvr_data.mem_base, GPIO_MEM_SIZE); } printk("%s:gpio_close-Driver unload complete\n", DRIVERNAME); } /***************************************************************************** Description: This function is called when the driver interface is opened Parameters: none Returns: 0 => success < 0 => error ******************************************************************************/ int gpio_open(struct inode *inode, struct file *filp) { printk("%s:gpio_open-module opened\n", DRIVERNAME); return 0; } /***************************************************************************** Description: This function is called when the driver interface is closed Parameters: none Returns: 0 => success < 0 => error ******************************************************************************/ int gpio_release(struct inode *inode, struct file *filp) { printk("%s:gpio_release-module released\n", DRIVERNAME); return 0; } /***************************************************************************** Description: This function is the ioctl interface to the driver. Parameters: inode ptr to inode filp ptr to file struct cmd ioctl command arg passed in argument Returns: 0 => success < 0 => error ******************************************************************************/ int gpio_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { gpio_ioctl_t Info; u_int bitstr = 0; u_int io_addr = 0; /* get the ioctl struct passed in by user */ if ( copy_from_user(&Info, (gpio_ioctl_t*)arg, sizeof(gpio_ioctl_t)) ) { printk("%s:gpio_ioctl-could not copy user space data.\n", DRIVERNAME); return -EFAULT; } switch ( cmd ) { case IOCTL_GET_PIN_INFO: /* make sure passed in signal is within max */ if ( Info.signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } /* fill buff with info about gpio signal */ gpio_getpininfo(Info.signal, Info.buff); break; case IOCTL_SET_TO_ALTERNATIVE: case IOCTL_SET_TO_GPIO: /* make sure passed in signal is within max */ if ( Info.signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } /* set register to use */ #ifdef PCH (Info.signal < (MAX_GPIO_SIGNALS/SEL)) ? ( io_addr = g_drvr_data.regs.gpio_use_sel ) : ( (Info.signal < ((MAX_GPIO_SIGNALS/SEL) + 32)) ? ( io_addr = g_drvr_data.regs.gpio_use_sel2 ) : ( io_addr = g_drvr_data.regs.gpio_use_sel3 ) ); #else Info.signal < (MAX_GPIO_SIGNALS/SEL) ? (io_addr = g_drvr_data.regs.gpio_use_sel) : (io_addr = g_drvr_data.regs.gpio_use_sel2); #endif /* get the register contents */ bitstr = inl(io_addr); if ( cmd == IOCTL_SET_TO_ALTERNATIVE ) { /* clear bit to use pin for alternative function */ clear_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } else { /* set bit to use pin for gpio */ set_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } /* write the modified string back to the gpio register */ outl(bitstr, io_addr); break; case IOCTL_SET_AS_INPUT: case IOCTL_SET_AS_OUTPUT: /* make sure passed in signal is within max */ if ( Info.signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } /* set register to use */ #ifdef PCH (Info.signal < (MAX_GPIO_SIGNALS/SEL)) ? ( io_addr = g_drvr_data.regs.gp_io_sel ) : ( (Info.signal < ((MAX_GPIO_SIGNALS/SEL) + 32)) ? ( io_addr = g_drvr_data.regs.gp_io_sel2 ) : ( io_addr = g_drvr_data.regs.gp_io_sel3 ) ); #else Info.signal < (MAX_GPIO_SIGNALS/SEL) ? (io_addr = g_drvr_data.regs.gp_io_sel) : (io_addr = g_drvr_data.regs.gp_io_sel2); #endif /* get the register contents */ bitstr = inl(io_addr); if ( cmd == IOCTL_SET_AS_OUTPUT ) { /* clear bit to use pin as output */ clear_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } else { /* set bit to use pin as input */ set_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } /* write the modified string back to the gpio register */ outl(bitstr, io_addr); break; case IOCTL_SET_HIGH: case IOCTL_SET_LOW: /* make sure passed in signal is within max */ if ( Info.signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } /* set register to use */ #ifdef PCH (Info.signal < (MAX_GPIO_SIGNALS/LVL)) ? ( io_addr = g_drvr_data.regs.gp_lvl ) : ( (Info.signal < ((MAX_GPIO_SIGNALS/LVL) + 32)) ? ( io_addr = g_drvr_data.regs.gp_lvl2 ) : ( io_addr = g_drvr_data.regs.gp_lvl3 ) ); #else Info.signal < (MAX_GPIO_SIGNALS/LVL) ? (io_addr = g_drvr_data.regs.gp_lvl) : (io_addr = g_drvr_data.regs.gp_lvl2); #endif /* get the register contents */ bitstr = inl(io_addr); if ( cmd == IOCTL_SET_LOW ) { /* write a 0 to output pin */ clear_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } else { /* write a 1 to output pin */ set_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } /* write the modified string back to the gpio register */ outl(bitstr, io_addr); break; case IOCTL_START_BLINK: case IOCTL_STOP_BLINK: /* make sure passed in signal is within max */ if ( Info.signal > GPIO_REG_MSB ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } bitstr = inl(g_drvr_data.regs.gpo_blink); if ( cmd == IOCTL_STOP_BLINK ) { /* set pin to non-blink mode */ clear_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } else { /* set pin to blink mode */ set_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } /* write the modified string back to the gpio register */ outl(bitstr, g_drvr_data.regs.gpo_blink); break; case IOCTL_INVERTED_INPUT: case IOCTL_NONINVERTED_INPUT: /* make sure passed in signal is within max */ if ( Info.signal > GPIO_REG_MSB ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } bitstr = inl(g_drvr_data.regs.gpi_inv); if ( cmd == IOCTL_NONINVERTED_INPUT ) { /* set pin to noninverted mode */ clear_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } else { /* set pin to inverted mode */ set_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr); } /* write the modified string back to the gpio register */ outl(bitstr, g_drvr_data.regs.gpi_inv); break; case IOCTL_READ_LEVEL: /* make sure passed in signal is within max */ if ( Info.signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_ioctl-signal value is out of bounds.\n", DRIVERNAME); return -EINVAL; } /* set register to use */ #ifdef PCH (Info.signal < (MAX_GPIO_SIGNALS/LVL)) ? ( io_addr = g_drvr_data.regs.gp_lvl ) : ( (Info.signal < ((MAX_GPIO_SIGNALS/LVL) + 32)) ? ( io_addr = g_drvr_data.regs.gp_lvl2 ) : ( io_addr = g_drvr_data.regs.gp_lvl3 ) ); #else Info.signal < (MAX_GPIO_SIGNALS/LVL) ? (io_addr = g_drvr_data.regs.gp_lvl) : (io_addr = g_drvr_data.regs.gp_lvl2); #endif /* get the register contents */ bitstr = inl(io_addr); test_bit(Info.signal%GPIO_REG_BITS, (void*)&bitstr) ? (Info.data = 1) : (Info.data = 0); break; case IOCTL_READ_GPIO_USE_SEL1_DWORD: Info.data = inl(g_drvr_data.regs.gpio_use_sel); break; case IOCTL_READ_GP_IO_SEL1_DWORD: Info.data = inl(g_drvr_data.regs.gp_io_sel); break; case IOCTL_READ_GP_LVL1_DWORD: Info.data = inl(g_drvr_data.regs.gp_lvl); break; case IOCTL_READ_GPO_BLINK_DWORD: Info.data = inl(g_drvr_data.regs.gpo_blink); break; case IOCTL_READ_GPI_INV_DWORD: Info.data = inl(g_drvr_data.regs.gpi_inv); break; case IOCTL_READ_GPIO_USE_SEL2_DWORD: Info.data = inl(g_drvr_data.regs.gpio_use_sel2); break; case IOCTL_READ_GP_IO_SEL2_DWORD: Info.data = inl(g_drvr_data.regs.gp_io_sel2); break; case IOCTL_READ_GP_LVL2_DWORD: Info.data = inl(g_drvr_data.regs.gp_lvl2); break; case IOCTL_WRITE_GPIO_USE_SEL1_DWORD: outl(Info.data, g_drvr_data.regs.gpio_use_sel); break; case IOCTL_WRITE_GP_IO_SEL1_DWORD: outl(Info.data, g_drvr_data.regs.gp_io_sel); break; case IOCTL_WRITE_GP_LVL1_DWORD: outl(Info.data, g_drvr_data.regs.gp_lvl); break; case IOCTL_WRITE_GPO_BLINK_DWORD: outl(Info.data, g_drvr_data.regs.gpo_blink); break; case IOCTL_WRITE_GPI_INV_DWORD: outl(Info.data, g_drvr_data.regs.gpi_inv); break; case IOCTL_WRITE_GPIO_USE_SEL2_DWORD: outl(Info.data, g_drvr_data.regs.gpio_use_sel2); break; case IOCTL_WRITE_GP_IO_SEL2_DWORD: outl(Info.data, g_drvr_data.regs.gp_io_sel2); break; case IOCTL_WRITE_GP_LVL2_DWORD: outl(Info.data, g_drvr_data.regs.gp_lvl2); break; #ifdef PCH case IOCTL_READ_GPIO_USE_SEL3_DWORD: Info.data = inl(g_drvr_data.regs.gpio_use_sel3); break; case IOCTL_READ_GP_IO_SEL3_DWORD: Info.data = inl(g_drvr_data.regs.gp_io_sel3); break; case IOCTL_READ_GP_LVL3_DWORD: Info.data = inl(g_drvr_data.regs.gp_lvl3); break; case IOCTL_WRITE_GPIO_USE_SEL3_DWORD: outl(Info.data, g_drvr_data.regs.gpio_use_sel3); break; case IOCTL_WRITE_GP_IO_SEL3_DWORD: outl(Info.data, g_drvr_data.regs.gp_io_sel3); break; case IOCTL_WRITE_GP_LVL3_DWORD: outl(Info.data, g_drvr_data.regs.gp_lvl3); break; #endif default: printk("%s:gpio_ioctl-unknown command (%d)\n", DRIVERNAME, cmd); } /* pass the ioctl struct back to the user */ if ( copy_to_user((gpio_ioctl_t*)arg, &Info, sizeof(gpio_ioctl_t)) ) { printk("%s:gpio_ioctl-could not copy data to user space.\n", DRIVERNAME); return -EFAULT; } return 0; } /***************************************************************************** Description: Set i/o memory ports. Parameters: addr start address of gpio registers Returns: void ******************************************************************************/ void set_reg_addrs(unsigned int addr) { g_drvr_data.regs.gpio_use_sel = addr + GPIO_USE_SEL; g_drvr_data.regs.gp_io_sel = addr + GP_IO_SEL; g_drvr_data.regs.gp_lvl = addr + GP_LVL; g_drvr_data.regs.gpo_blink = addr + GPO_BLINK; g_drvr_data.regs.gpi_inv = addr + GPI_INV; g_drvr_data.regs.gpio_use_sel2 = addr + GPIO_USE_SEL2; g_drvr_data.regs.gp_io_sel2 = addr + GP_IO_SEL2; g_drvr_data.regs.gp_lvl2 = addr + GP_LVL2; #ifdef PCH g_drvr_data.regs.gpio_use_sel3 = addr + GPIO_USE_SEL3; g_drvr_data.regs.gp_io_sel3 = addr + GP_IO_SEL3; g_drvr_data.regs.gp_lvl3 = addr + GP_LVL3; #endif } /***************************************************************************** Description: Creates a string that reflects the current state of a gpio signal. Parameters: Signal signal number to return info on pBuff string buff to hold returned signal info Returns: 0 => success < 0 => error ******************************************************************************/ int gpio_getpininfo(int Signal, char *pBuff) { unsigned int data; /* make sure passed in signal is within max */ if ( Signal >= MAX_GPIO_SIGNALS ) { printk("%s:gpio_getpininfo-signal value is out of bounds (%d)\n", DRIVERNAME, Signal); return -1; } sprintf(pBuff, "Signal# %d - ", Signal); /* get current use setting: alternative or gpio */ strcat(pBuff, "Use setting:"); #ifdef PCH (Signal < (MAX_GPIO_SIGNALS/SEL)) ? ( data = inl(g_drvr_data.regs.gpio_use_sel) ) : ( (Signal < ((MAX_GPIO_SIGNALS/SEL) + 32)) ? ( data = inl(g_drvr_data.regs.gpio_use_sel2) ) : ( data = inl(g_drvr_data.regs.gpio_use_sel3) ) ); #else Signal < (MAX_GPIO_SIGNALS/SEL) ? (data = inl(g_drvr_data.regs.gpio_use_sel)) : (data = inl(g_drvr_data.regs.gpio_use_sel2)); #endif /* if set as gpio */ if ( test_bit(Signal%GPIO_REG_BITS, (void*)&data) ) { strcat(pBuff, "gpio"); } else { strcat(pBuff, "alternative"); } strcat(pBuff, ", "); /* get current io setting: input or output */ strcat(pBuff, "IO setting:"); #ifdef PCH (Signal < (MAX_GPIO_SIGNALS/SEL)) ? ( data = inl(g_drvr_data.regs.gp_io_sel) ) : ( (Signal < ((MAX_GPIO_SIGNALS/SEL) + 32)) ? ( data = inl(g_drvr_data.regs.gp_io_sel2) ) : ( data = inl(g_drvr_data.regs.gp_io_sel3) ) ); #else Signal < (MAX_GPIO_SIGNALS/SEL) ? (data = inl(g_drvr_data.regs.gp_io_sel)) : (data = inl(g_drvr_data.regs.gp_io_sel2)); #endif /* if set as input */ if ( test_bit(Signal%GPIO_REG_BITS, (void*)&data) ) { strcat(pBuff, "input"); } else { strcat(pBuff, "output"); } strcat(pBuff, ", "); /* get current pin level: high or low */ strcat(pBuff, "Level:"); #ifdef PCH (Signal < (MAX_GPIO_SIGNALS/LVL)) ? ( data = inl(g_drvr_data.regs.gp_lvl) ) : ( (Signal < ((MAX_GPIO_SIGNALS/LVL) + 32)) ? ( data = inl(g_drvr_data.regs.gp_lvl2) ) : ( data = inl(g_drvr_data.regs.gp_lvl3) ) ); #else Signal < (MAX_GPIO_SIGNALS/LVL) ? (data = inl(g_drvr_data.regs.gp_lvl)) : (data = inl(g_drvr_data.regs.gp_lvl2)); #endif /* if high */ if ( test_bit(Signal%GPIO_REG_BITS, (void*)&data) ) { strcat(pBuff, "high"); } else { strcat(pBuff, "low"); } strcat(pBuff, ", "); /* get blink setting */ strcat(pBuff, "Blink:"); data = inl(g_drvr_data.regs.gpo_blink); /* current setting: on or off */ if ( test_bit(Signal%GPIO_REG_BITS, (void*)&data) ) { strcat(pBuff, "on"); } else { strcat(pBuff, "off"); } strcat(pBuff, ", "); /* get inversion setting */ strcat(pBuff, "Inversion:"); data = inl(g_drvr_data.regs.gpi_inv); /* current setting: inverted or noninverted */ if ( test_bit(Signal%GPIO_REG_BITS, (void*)&data) ) { strcat(pBuff, "on"); } else { strcat(pBuff, "off"); } return 0; } module_init(gpio_init); module_exit(gpio_close);