2020-12-03 21:24:23 +03:00
// SPDX-License-Identifier: GPL-2.0
2012-06-01 17:36:31 +04:00
/*
* GPIO driver for AMD 8111 south bridges
*
* Copyright ( c ) 2012 Dmitry Eremin - Solenikov
*
* Based on the AMD RNG driver :
* Copyright 2005 ( c ) MontaVista Software , Inc .
* with the majority of the code coming from :
*
* Hardware driver for the Intel / AMD / VIA Random Number Generators ( RNG )
* ( c ) Copyright 2003 Red Hat Inc < jgarzik @ redhat . com >
*
* derived from
*
* Hardware driver for the AMD 768 Random Number Generator ( RNG )
* ( c ) Copyright 2001 Red Hat Inc
*
* derived from
*
* Hardware driver for Intel i810 Random Number Generator ( RNG )
* Copyright 2000 , 2001 Jeff Garzik < jgarzik @ pobox . com >
* Copyright 2000 , 2001 Philipp Rumpf < prumpf @ mandrakesoft . com >
*/
2016-02-03 23:17:13 +03:00
# include <linux/ioport.h>
2012-06-01 17:36:31 +04:00
# include <linux/module.h>
# include <linux/kernel.h>
2018-01-14 00:22:49 +03:00
# include <linux/gpio/driver.h>
2012-06-01 17:36:31 +04:00
# include <linux/pci.h>
# include <linux/spinlock.h>
# define PMBASE_OFFSET 0xb0
# define PMBASE_SIZE 0x30
# define AMD_REG_GPIO(i) (0x10 + (i))
# define AMD_GPIO_LTCH_STS 0x40 /* Latch status, w1 */
# define AMD_GPIO_RTIN 0x20 /* Real Time in, ro */
# define AMD_GPIO_DEBOUNCE 0x10 /* Debounce, rw */
# define AMD_GPIO_MODE_MASK 0x0c /* Pin Mode Select, rw */
# define AMD_GPIO_MODE_IN 0x00
# define AMD_GPIO_MODE_OUT 0x04
/* Enable alternative (e.g. clkout, IRQ, etc) function of the pin */
# define AMD_GPIO_MODE_ALTFN 0x08 /* Or 0x09 */
# define AMD_GPIO_X_MASK 0x03 /* In/Out specific, rw */
# define AMD_GPIO_X_IN_ACTIVEHI 0x01 /* Active High */
# define AMD_GPIO_X_IN_LATCH 0x02 /* Latched version is selected */
# define AMD_GPIO_X_OUT_LOW 0x00
# define AMD_GPIO_X_OUT_HI 0x01
# define AMD_GPIO_X_OUT_CLK0 0x02
# define AMD_GPIO_X_OUT_CLK1 0x03
/*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE . We do not actually
* register a pci_driver , because someone else might one day
* want to register another driver on the same PCI id .
*/
2013-12-03 03:08:45 +04:00
static const struct pci_device_id pci_tbl [ ] = {
2012-06-01 17:36:31 +04:00
{ PCI_DEVICE ( PCI_VENDOR_ID_AMD , PCI_DEVICE_ID_AMD_8111_SMBUS ) , 0 } ,
{ 0 , } , /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , pci_tbl ) ;
struct amd_gpio {
struct gpio_chip chip ;
u32 pmbase ;
void __iomem * pm ;
struct pci_dev * pdev ;
spinlock_t lock ; /* guards hw registers and orig table */
u8 orig [ 32 ] ;
} ;
static int amd_gpio_request ( struct gpio_chip * chip , unsigned offset )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
agp - > orig [ offset ] = ioread8 ( agp - > pm + AMD_REG_GPIO ( offset ) ) &
( AMD_GPIO_DEBOUNCE | AMD_GPIO_MODE_MASK | AMD_GPIO_X_MASK ) ;
dev_dbg ( & agp - > pdev - > dev , " Requested gpio %d, data %x \n " , offset , agp - > orig [ offset ] ) ;
return 0 ;
}
static void amd_gpio_free ( struct gpio_chip * chip , unsigned offset )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
dev_dbg ( & agp - > pdev - > dev , " Freed gpio %d, data %x \n " , offset , agp - > orig [ offset ] ) ;
iowrite8 ( agp - > orig [ offset ] , agp - > pm + AMD_REG_GPIO ( offset ) ) ;
}
static void amd_gpio_set ( struct gpio_chip * chip , unsigned offset , int value )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
u8 temp ;
unsigned long flags ;
spin_lock_irqsave ( & agp - > lock , flags ) ;
temp = ioread8 ( agp - > pm + AMD_REG_GPIO ( offset ) ) ;
temp = ( temp & AMD_GPIO_DEBOUNCE ) | AMD_GPIO_MODE_OUT | ( value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW ) ;
iowrite8 ( temp , agp - > pm + AMD_REG_GPIO ( offset ) ) ;
spin_unlock_irqrestore ( & agp - > lock , flags ) ;
dev_dbg ( & agp - > pdev - > dev , " Setting gpio %d, value %d, reg=%02x \n " , offset , ! ! value , temp ) ;
}
static int amd_gpio_get ( struct gpio_chip * chip , unsigned offset )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
u8 temp ;
temp = ioread8 ( agp - > pm + AMD_REG_GPIO ( offset ) ) ;
dev_dbg ( & agp - > pdev - > dev , " Getting gpio %d, reg=%02x \n " , offset , temp ) ;
return ( temp & AMD_GPIO_RTIN ) ? 1 : 0 ;
}
static int amd_gpio_dirout ( struct gpio_chip * chip , unsigned offset , int value )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
u8 temp ;
unsigned long flags ;
spin_lock_irqsave ( & agp - > lock , flags ) ;
temp = ioread8 ( agp - > pm + AMD_REG_GPIO ( offset ) ) ;
temp = ( temp & AMD_GPIO_DEBOUNCE ) | AMD_GPIO_MODE_OUT | ( value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW ) ;
iowrite8 ( temp , agp - > pm + AMD_REG_GPIO ( offset ) ) ;
spin_unlock_irqrestore ( & agp - > lock , flags ) ;
dev_dbg ( & agp - > pdev - > dev , " Dirout gpio %d, value %d, reg=%02x \n " , offset , ! ! value , temp ) ;
return 0 ;
}
static int amd_gpio_dirin ( struct gpio_chip * chip , unsigned offset )
{
2015-12-04 17:26:35 +03:00
struct amd_gpio * agp = gpiochip_get_data ( chip ) ;
2012-06-01 17:36:31 +04:00
u8 temp ;
unsigned long flags ;
spin_lock_irqsave ( & agp - > lock , flags ) ;
temp = ioread8 ( agp - > pm + AMD_REG_GPIO ( offset ) ) ;
temp = ( temp & AMD_GPIO_DEBOUNCE ) | AMD_GPIO_MODE_IN ;
iowrite8 ( temp , agp - > pm + AMD_REG_GPIO ( offset ) ) ;
spin_unlock_irqrestore ( & agp - > lock , flags ) ;
dev_dbg ( & agp - > pdev - > dev , " Dirin gpio %d, reg=%02x \n " , offset , temp ) ;
return 0 ;
}
static struct amd_gpio gp = {
. chip = {
. label = " AMD GPIO " ,
. owner = THIS_MODULE ,
. base = - 1 ,
. ngpio = 32 ,
. request = amd_gpio_request ,
. free = amd_gpio_free ,
. set = amd_gpio_set ,
. get = amd_gpio_get ,
. direction_output = amd_gpio_dirout ,
. direction_input = amd_gpio_dirin ,
} ,
} ;
static int __init amd_gpio_init ( void )
{
int err = - ENODEV ;
struct pci_dev * pdev = NULL ;
const struct pci_device_id * ent ;
/* We look for our device - AMD South Bridge
* I don ' t know about a system with two such bridges ,
* so we can assume that there is max . one device .
*
* We can ' t use plain pci_driver mechanism ,
* as the device is really a multiple function device ,
* main driver that binds to the pci_device is an smbus
* driver and have to find & bind to the device this way .
*/
for_each_pci_dev ( pdev ) {
ent = pci_match_id ( pci_tbl , pdev ) ;
if ( ent )
goto found ;
}
/* Device not found. */
goto out ;
found :
err = pci_read_config_dword ( pdev , 0x58 , & gp . pmbase ) ;
if ( err )
goto out ;
err = - EIO ;
gp . pmbase & = 0x0000FF00 ;
if ( gp . pmbase = = 0 )
goto out ;
2016-02-03 23:17:13 +03:00
if ( ! devm_request_region ( & pdev - > dev , gp . pmbase + PMBASE_OFFSET ,
PMBASE_SIZE , " AMD GPIO " ) ) {
2012-06-01 17:36:31 +04:00
dev_err ( & pdev - > dev , " AMD GPIO region 0x%x already in use! \n " ,
gp . pmbase + PMBASE_OFFSET ) ;
err = - EBUSY ;
goto out ;
}
gp . pm = ioport_map ( gp . pmbase + PMBASE_OFFSET , PMBASE_SIZE ) ;
2014-12-29 11:39:01 +03:00
if ( ! gp . pm ) {
dev_err ( & pdev - > dev , " Couldn't map io port into io memory \n " ) ;
err = - ENOMEM ;
goto out ;
}
2012-06-01 17:36:31 +04:00
gp . pdev = pdev ;
2015-11-04 11:56:26 +03:00
gp . chip . parent = & pdev - > dev ;
2012-06-01 17:36:31 +04:00
spin_lock_init ( & gp . lock ) ;
2020-12-03 21:24:22 +03:00
dev_info ( & pdev - > dev , " AMD-8111 GPIO detected \n " ) ;
2015-12-04 17:26:35 +03:00
err = gpiochip_add_data ( & gp . chip , & gp ) ;
2012-06-01 17:36:31 +04:00
if ( err ) {
2020-12-03 21:24:22 +03:00
dev_err ( & pdev - > dev , " GPIO registering failed (%d) \n " , err ) ;
2014-10-01 14:03:00 +04:00
ioport_unmap ( gp . pm ) ;
2012-06-01 17:36:31 +04:00
goto out ;
}
out :
return err ;
}
static void __exit amd_gpio_exit ( void )
{
2014-07-13 00:30:12 +04:00
gpiochip_remove ( & gp . chip ) ;
2012-06-01 17:36:31 +04:00
ioport_unmap ( gp . pm ) ;
}
module_init ( amd_gpio_init ) ;
module_exit ( amd_gpio_exit ) ;
MODULE_AUTHOR ( " The Linux Kernel team " ) ;
MODULE_DESCRIPTION ( " GPIO driver for AMD chipsets " ) ;
MODULE_LICENSE ( " GPL " ) ;