2023-03-02 11:17:11 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Loongson GPIO Support
*
* Copyright ( C ) 2022 - 2023 Loongson Technology Corporation Limited
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/spinlock.h>
# include <linux/err.h>
# include <linux/gpio/driver.h>
# include <linux/platform_device.h>
# include <linux/bitops.h>
# include <asm/types.h>
enum loongson_gpio_mode {
BIT_CTRL_MODE ,
BYTE_CTRL_MODE ,
} ;
struct loongson_gpio_chip_data {
const char * label ;
enum loongson_gpio_mode mode ;
unsigned int conf_offset ;
unsigned int out_offset ;
unsigned int in_offset ;
2023-09-21 04:52:47 +03:00
unsigned int inten_offset ;
2023-03-02 11:17:11 +03:00
} ;
struct loongson_gpio_chip {
struct gpio_chip chip ;
struct fwnode_handle * fwnode ;
spinlock_t lock ;
void __iomem * reg_base ;
const struct loongson_gpio_chip_data * chip_data ;
} ;
static inline struct loongson_gpio_chip * to_loongson_gpio_chip ( struct gpio_chip * chip )
{
return container_of ( chip , struct loongson_gpio_chip , chip ) ;
}
static inline void loongson_commit_direction ( struct loongson_gpio_chip * lgpio , unsigned int pin ,
int input )
{
u8 bval = input ? 1 : 0 ;
writeb ( bval , lgpio - > reg_base + lgpio - > chip_data - > conf_offset + pin ) ;
}
static void loongson_commit_level ( struct loongson_gpio_chip * lgpio , unsigned int pin , int high )
{
u8 bval = high ? 1 : 0 ;
writeb ( bval , lgpio - > reg_base + lgpio - > chip_data - > out_offset + pin ) ;
}
static int loongson_gpio_direction_input ( struct gpio_chip * chip , unsigned int pin )
{
unsigned long flags ;
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
spin_lock_irqsave ( & lgpio - > lock , flags ) ;
loongson_commit_direction ( lgpio , pin , 1 ) ;
spin_unlock_irqrestore ( & lgpio - > lock , flags ) ;
return 0 ;
}
static int loongson_gpio_direction_output ( struct gpio_chip * chip , unsigned int pin , int value )
{
unsigned long flags ;
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
spin_lock_irqsave ( & lgpio - > lock , flags ) ;
loongson_commit_level ( lgpio , pin , value ) ;
loongson_commit_direction ( lgpio , pin , 0 ) ;
spin_unlock_irqrestore ( & lgpio - > lock , flags ) ;
return 0 ;
}
static int loongson_gpio_get ( struct gpio_chip * chip , unsigned int pin )
{
u8 bval ;
int val ;
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
bval = readb ( lgpio - > reg_base + lgpio - > chip_data - > in_offset + pin ) ;
val = bval & 1 ;
return val ;
}
static int loongson_gpio_get_direction ( struct gpio_chip * chip , unsigned int pin )
{
u8 bval ;
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
bval = readb ( lgpio - > reg_base + lgpio - > chip_data - > conf_offset + pin ) ;
if ( bval & 1 )
return GPIO_LINE_DIRECTION_IN ;
return GPIO_LINE_DIRECTION_OUT ;
}
static void loongson_gpio_set ( struct gpio_chip * chip , unsigned int pin , int value )
{
unsigned long flags ;
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
spin_lock_irqsave ( & lgpio - > lock , flags ) ;
loongson_commit_level ( lgpio , pin , value ) ;
spin_unlock_irqrestore ( & lgpio - > lock , flags ) ;
}
static int loongson_gpio_to_irq ( struct gpio_chip * chip , unsigned int offset )
{
2023-09-21 04:52:47 +03:00
unsigned int u ;
2023-03-02 11:17:11 +03:00
struct platform_device * pdev = to_platform_device ( chip - > parent ) ;
2023-09-21 04:52:47 +03:00
struct loongson_gpio_chip * lgpio = to_loongson_gpio_chip ( chip ) ;
if ( lgpio - > chip_data - > mode = = BIT_CTRL_MODE ) {
/* Get the register index from offset then multiply by bytes per register */
u = readl ( lgpio - > reg_base + lgpio - > chip_data - > inten_offset + ( offset / 32 ) * 4 ) ;
u | = BIT ( offset % 32 ) ;
writel ( u , lgpio - > reg_base + lgpio - > chip_data - > inten_offset + ( offset / 32 ) * 4 ) ;
} else {
writeb ( 1 , lgpio - > reg_base + lgpio - > chip_data - > inten_offset + offset ) ;
}
2023-03-02 11:17:11 +03:00
return platform_get_irq ( pdev , offset ) ;
}
static int loongson_gpio_init ( struct device * dev , struct loongson_gpio_chip * lgpio ,
2023-09-21 04:52:47 +03:00
void __iomem * reg_base )
2023-03-02 11:17:11 +03:00
{
int ret ;
u32 ngpios ;
lgpio - > reg_base = reg_base ;
if ( lgpio - > chip_data - > mode = = BIT_CTRL_MODE ) {
ret = bgpio_init ( & lgpio - > chip , dev , 8 ,
lgpio - > reg_base + lgpio - > chip_data - > in_offset ,
lgpio - > reg_base + lgpio - > chip_data - > out_offset ,
NULL , NULL ,
lgpio - > reg_base + lgpio - > chip_data - > conf_offset ,
0 ) ;
if ( ret ) {
dev_err ( dev , " unable to init generic GPIO \n " ) ;
return ret ;
}
} else {
lgpio - > chip . direction_input = loongson_gpio_direction_input ;
lgpio - > chip . get = loongson_gpio_get ;
lgpio - > chip . get_direction = loongson_gpio_get_direction ;
lgpio - > chip . direction_output = loongson_gpio_direction_output ;
lgpio - > chip . set = loongson_gpio_set ;
lgpio - > chip . parent = dev ;
2023-09-21 04:52:47 +03:00
device_property_read_u32 ( dev , " ngpios " , & ngpios ) ;
lgpio - > chip . ngpio = ngpios ;
2023-03-02 11:17:11 +03:00
spin_lock_init ( & lgpio - > lock ) ;
}
lgpio - > chip . label = lgpio - > chip_data - > label ;
2023-09-21 04:52:47 +03:00
lgpio - > chip . can_sleep = false ;
if ( lgpio - > chip_data - > inten_offset )
lgpio - > chip . to_irq = loongson_gpio_to_irq ;
2023-03-02 11:17:11 +03:00
return devm_gpiochip_add_data ( dev , & lgpio - > chip , lgpio ) ;
}
static int loongson_gpio_probe ( struct platform_device * pdev )
{
void __iomem * reg_base ;
struct loongson_gpio_chip * lgpio ;
struct device * dev = & pdev - > dev ;
lgpio = devm_kzalloc ( dev , sizeof ( * lgpio ) , GFP_KERNEL ) ;
if ( ! lgpio )
return - ENOMEM ;
lgpio - > chip_data = device_get_match_data ( dev ) ;
reg_base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( reg_base ) )
return PTR_ERR ( reg_base ) ;
2023-09-21 04:52:47 +03:00
return loongson_gpio_init ( dev , lgpio , reg_base ) ;
2023-03-02 11:17:11 +03:00
}
static const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = {
. label = " ls2k_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0x20 ,
. out_offset = 0x10 ,
2023-09-21 04:52:47 +03:00
. inten_offset = 0x30 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data0 = {
. label = " ls2k0500_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0x8 ,
. out_offset = 0x10 ,
. inten_offset = 0xb0 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data1 = {
. label = " ls2k0500_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0x8 ,
. out_offset = 0x10 ,
. inten_offset = 0x98 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = {
. label = " ls2k2000_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0xc ,
. out_offset = 0x8 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data1 = {
. label = " ls2k2000_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0x20 ,
. out_offset = 0x10 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data2 = {
. label = " ls2k2000_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x84 ,
. in_offset = 0x88 ,
. out_offset = 0x80 ,
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls3a5000_data = {
. label = " ls3a5000_gpio " ,
. mode = BIT_CTRL_MODE ,
. conf_offset = 0x0 ,
. in_offset = 0xc ,
. out_offset = 0x8 ,
2023-03-02 11:17:11 +03:00
} ;
static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
. label = " ls7a_gpio " ,
. mode = BYTE_CTRL_MODE ,
. conf_offset = 0x800 ,
. in_offset = 0xa00 ,
. out_offset = 0x900 ,
} ;
static const struct of_device_id loongson_gpio_of_match [ ] = {
{
. compatible = " loongson,ls2k-gpio " ,
. data = & loongson_gpio_ls2k_data ,
} ,
2023-09-21 04:52:47 +03:00
{
. compatible = " loongson,ls2k0500-gpio0 " ,
. data = & loongson_gpio_ls2k0500_data0 ,
} ,
{
. compatible = " loongson,ls2k0500-gpio1 " ,
. data = & loongson_gpio_ls2k0500_data1 ,
} ,
{
. compatible = " loongson,ls2k2000-gpio0 " ,
. data = & loongson_gpio_ls2k2000_data0 ,
} ,
{
. compatible = " loongson,ls2k2000-gpio1 " ,
. data = & loongson_gpio_ls2k2000_data1 ,
} ,
{
. compatible = " loongson,ls2k2000-gpio2 " ,
. data = & loongson_gpio_ls2k2000_data2 ,
} ,
{
. compatible = " loongson,ls3a5000-gpio " ,
. data = & loongson_gpio_ls3a5000_data ,
} ,
2023-03-02 11:17:11 +03:00
{
. compatible = " loongson,ls7a-gpio " ,
. data = & loongson_gpio_ls7a_data ,
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , loongson_gpio_of_match ) ;
static const struct acpi_device_id loongson_gpio_acpi_match [ ] = {
{
. id = " LOON0002 " ,
. driver_data = ( kernel_ulong_t ) & loongson_gpio_ls7a_data ,
} ,
2023-09-21 04:52:47 +03:00
{
. id = " LOON0007 " ,
. driver_data = ( kernel_ulong_t ) & loongson_gpio_ls3a5000_data ,
} ,
{
. id = " LOON000A " ,
. driver_data = ( kernel_ulong_t ) & loongson_gpio_ls2k2000_data0 ,
} ,
{
. id = " LOON000B " ,
. driver_data = ( kernel_ulong_t ) & loongson_gpio_ls2k2000_data1 ,
} ,
{
. id = " LOON000C " ,
. driver_data = ( kernel_ulong_t ) & loongson_gpio_ls2k2000_data2 ,
} ,
2023-03-02 11:17:11 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , loongson_gpio_acpi_match ) ;
static struct platform_driver loongson_gpio_driver = {
. driver = {
. name = " loongson-gpio " ,
. of_match_table = loongson_gpio_of_match ,
. acpi_match_table = loongson_gpio_acpi_match ,
} ,
. probe = loongson_gpio_probe ,
} ;
static int __init loongson_gpio_setup ( void )
{
return platform_driver_register ( & loongson_gpio_driver ) ;
}
postcore_initcall ( loongson_gpio_setup ) ;
MODULE_DESCRIPTION ( " Loongson gpio driver " ) ;
MODULE_LICENSE ( " GPL " ) ;