2018-08-09 13:40:46 +02:00
// SPDX-License-Identifier: GPL-2.0+
/*
2022-01-21 17:24:27 +00:00
* Serial multi - instantiate driver , pseudo driver to instantiate multiple
* client devices from a single fwnode .
2018-08-09 13:40:46 +02:00
*
* Copyright 2018 Hans de Goede < hdegoede @ redhat . com >
*/
# include <linux/acpi.h>
2018-11-28 13:45:31 +02:00
# include <linux/bits.h>
2018-08-09 13:40:46 +02:00
# include <linux/i2c.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2020-11-05 13:05:30 +02:00
# include <linux/property.h>
2022-01-21 17:24:29 +00:00
# include <linux/spi/spi.h>
2018-11-28 13:45:30 +02:00
# include <linux/types.h>
2018-08-09 13:40:46 +02:00
2018-11-28 13:45:31 +02:00
# define IRQ_RESOURCE_TYPE GENMASK(1, 0)
# define IRQ_RESOURCE_NONE 0
# define IRQ_RESOURCE_GPIO 1
2018-11-28 13:45:32 +02:00
# define IRQ_RESOURCE_APIC 2
2018-11-28 13:45:31 +02:00
2022-01-21 17:24:29 +00:00
enum smi_bus_type {
SMI_I2C ,
SMI_SPI ,
SMI_AUTO_DETECT ,
} ;
2022-01-21 17:24:27 +00:00
struct smi_instance {
2018-08-09 13:40:46 +02:00
const char * type ;
2018-11-28 13:45:31 +02:00
unsigned int flags ;
int irq_idx ;
2018-08-09 13:40:46 +02:00
} ;
2022-01-21 17:24:29 +00:00
struct smi_node {
enum smi_bus_type bus_type ;
struct smi_instance instances [ ] ;
} ;
2022-01-21 17:24:27 +00:00
struct smi {
int i2c_num ;
2022-01-21 17:24:29 +00:00
int spi_num ;
2022-01-21 17:24:28 +00:00
struct i2c_client * * i2c_devs ;
2022-01-21 17:24:29 +00:00
struct spi_device * * spi_devs ;
2018-08-09 13:40:46 +02:00
} ;
2022-01-21 17:24:28 +00:00
static int smi_get_irq ( struct platform_device * pdev , struct acpi_device * adev ,
const struct smi_instance * inst )
{
int ret ;
switch ( inst - > flags & IRQ_RESOURCE_TYPE ) {
case IRQ_RESOURCE_GPIO :
ret = acpi_dev_gpio_irq_get ( adev , inst - > irq_idx ) ;
break ;
case IRQ_RESOURCE_APIC :
ret = platform_get_irq ( pdev , inst - > irq_idx ) ;
break ;
default :
return 0 ;
}
if ( ret < 0 )
2022-07-10 00:16:50 +03:00
return dev_err_probe ( & pdev - > dev , ret , " Error requesting irq at index %d \n " ,
inst - > irq_idx ) ;
2022-01-21 17:24:28 +00:00
return ret ;
}
static void smi_devs_unregister ( struct smi * smi )
{
2022-07-10 00:16:51 +03:00
while ( smi - > i2c_num - - )
i2c_unregister_device ( smi - > i2c_devs [ smi - > i2c_num ] ) ;
2022-01-21 17:24:29 +00:00
2022-07-10 00:16:51 +03:00
while ( smi - > spi_num - - )
spi_unregister_device ( smi - > spi_devs [ smi - > spi_num ] ) ;
2022-01-21 17:24:29 +00:00
}
/**
* smi_spi_probe - Instantiate multiple SPI devices from inst array
* @ pdev : Platform device
* @ smi : Internal struct for Serial multi instantiate driver
* @ inst_array : Array of instances to probe
*
* Returns the number of SPI devices instantiate , Zero if none is found or a negative error code .
*/
2022-07-10 00:16:49 +03:00
static int smi_spi_probe ( struct platform_device * pdev , struct smi * smi ,
2022-01-21 17:24:29 +00:00
const struct smi_instance * inst_array )
{
struct device * dev = & pdev - > dev ;
2022-07-10 00:16:49 +03:00
struct acpi_device * adev = ACPI_COMPANION ( dev ) ;
2022-01-21 17:24:29 +00:00
struct spi_controller * ctlr ;
struct spi_device * spi_dev ;
char name [ 50 ] ;
int i , ret , count ;
ret = acpi_spi_count_resources ( adev ) ;
if ( ret < 0 )
return ret ;
2022-07-10 00:16:52 +03:00
if ( ! ret )
2022-07-10 00:16:48 +03:00
return - ENOENT ;
2022-01-21 17:24:29 +00:00
count = ret ;
smi - > spi_devs = devm_kcalloc ( dev , count , sizeof ( * smi - > spi_devs ) , GFP_KERNEL ) ;
if ( ! smi - > spi_devs )
return - ENOMEM ;
for ( i = 0 ; i < count & & inst_array [ i ] . type ; i + + ) {
spi_dev = acpi_spi_device_alloc ( NULL , adev , i ) ;
if ( IS_ERR ( spi_dev ) ) {
2022-07-10 00:16:50 +03:00
ret = dev_err_probe ( dev , PTR_ERR ( spi_dev ) , " failed to allocate SPI device %s from ACPI \n " ,
dev_name ( & adev - > dev ) ) ;
2022-01-21 17:24:29 +00:00
goto error ;
}
ctlr = spi_dev - > controller ;
strscpy ( spi_dev - > modalias , inst_array [ i ] . type , sizeof ( spi_dev - > modalias ) ) ;
ret = smi_get_irq ( pdev , adev , & inst_array [ i ] ) ;
if ( ret < 0 ) {
spi_dev_put ( spi_dev ) ;
goto error ;
}
spi_dev - > irq = ret ;
snprintf ( name , sizeof ( name ) , " %s-%s-%s.%d " , dev_name ( & ctlr - > dev ) , dev_name ( dev ) ,
inst_array [ i ] . type , i ) ;
spi_dev - > dev . init_name = name ;
ret = spi_add_device ( spi_dev ) ;
if ( ret ) {
2022-07-10 00:16:50 +03:00
dev_err_probe ( & ctlr - > dev , ret , " failed to add SPI device %s from ACPI \n " ,
dev_name ( & adev - > dev ) ) ;
2022-01-21 17:24:29 +00:00
spi_dev_put ( spi_dev ) ;
goto error ;
}
dev_dbg ( dev , " SPI device %s using chip select %u " , name , spi_dev - > chip_select ) ;
smi - > spi_devs [ i ] = spi_dev ;
smi - > spi_num + + ;
}
if ( smi - > spi_num < count ) {
dev_dbg ( dev , " Error finding driver, idx %d \n " , i ) ;
ret = - ENODEV ;
goto error ;
}
dev_info ( dev , " Instantiated %d SPI devices. \n " , smi - > spi_num ) ;
return 0 ;
error :
smi_devs_unregister ( smi ) ;
return ret ;
2022-01-21 17:24:28 +00:00
}
/**
* smi_i2c_probe - Instantiate multiple I2C devices from inst array
* @ pdev : Platform device
* @ smi : Internal struct for Serial multi instantiate driver
* @ inst_array : Array of instances to probe
*
* Returns the number of I2C devices instantiate , Zero if none is found or a negative error code .
*/
2022-07-10 00:16:49 +03:00
static int smi_i2c_probe ( struct platform_device * pdev , struct smi * smi ,
2022-01-21 17:24:28 +00:00
const struct smi_instance * inst_array )
2018-08-09 13:40:46 +02:00
{
struct i2c_board_info board_info = { } ;
struct device * dev = & pdev - > dev ;
2022-07-10 00:16:49 +03:00
struct acpi_device * adev = ACPI_COMPANION ( dev ) ;
2018-08-09 13:40:46 +02:00
char name [ 32 ] ;
2022-01-21 17:24:28 +00:00
int i , ret , count ;
2018-08-09 13:40:46 +02:00
2021-08-03 18:00:43 +02:00
ret = i2c_acpi_client_count ( adev ) ;
2018-11-28 13:45:30 +02:00
if ( ret < 0 )
return ret ;
2022-07-10 00:16:52 +03:00
if ( ! ret )
2022-07-10 00:16:48 +03:00
return - ENOENT ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
count = ret ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
smi - > i2c_devs = devm_kcalloc ( dev , count , sizeof ( * smi - > i2c_devs ) , GFP_KERNEL ) ;
if ( ! smi - > i2c_devs )
return - ENOMEM ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
for ( i = 0 ; i < count & & inst_array [ i ] . type ; i + + ) {
2018-08-09 13:40:46 +02:00
memset ( & board_info , 0 , sizeof ( board_info ) ) ;
2022-01-21 17:24:28 +00:00
strscpy ( board_info . type , inst_array [ i ] . type , I2C_NAME_SIZE ) ;
snprintf ( name , sizeof ( name ) , " %s-%s.%d " , dev_name ( dev ) , inst_array [ i ] . type , i ) ;
2018-08-09 13:40:46 +02:00
board_info . dev_name = name ;
2022-01-21 17:24:28 +00:00
ret = smi_get_irq ( pdev , adev , & inst_array [ i ] ) ;
if ( ret < 0 )
goto error ;
board_info . irq = ret ;
2022-01-21 17:24:27 +00:00
smi - > i2c_devs [ i ] = i2c_acpi_new_device ( dev , i , & board_info ) ;
if ( IS_ERR ( smi - > i2c_devs [ i ] ) ) {
ret = dev_err_probe ( dev , PTR_ERR ( smi - > i2c_devs [ i ] ) ,
2020-11-05 13:05:28 +02:00
" Error creating i2c-client, idx %d \n " , i ) ;
2018-08-09 13:40:46 +02:00
goto error ;
}
2022-01-21 17:24:28 +00:00
smi - > i2c_num + + ;
2018-08-09 13:40:46 +02:00
}
2022-01-21 17:24:28 +00:00
if ( smi - > i2c_num < count ) {
dev_dbg ( dev , " Error finding driver, idx %d \n " , i ) ;
2018-11-28 13:45:30 +02:00
ret = - ENODEV ;
goto error ;
}
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
dev_info ( dev , " Instantiated %d I2C devices. \n " , smi - > i2c_num ) ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
return 0 ;
2018-08-09 13:40:46 +02:00
error :
2022-01-21 17:24:28 +00:00
smi_devs_unregister ( smi ) ;
2018-08-09 13:40:46 +02:00
return ret ;
}
2022-01-21 17:24:28 +00:00
static int smi_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2022-01-21 17:24:29 +00:00
const struct smi_node * node ;
2022-01-21 17:24:28 +00:00
struct smi * smi ;
2022-07-10 00:16:48 +03:00
int ret ;
2022-01-21 17:24:28 +00:00
2022-01-21 17:24:29 +00:00
node = device_get_match_data ( dev ) ;
if ( ! node ) {
2022-01-21 17:24:28 +00:00
dev_dbg ( dev , " Error ACPI match data is missing \n " ) ;
return - ENODEV ;
}
smi = devm_kzalloc ( dev , sizeof ( * smi ) , GFP_KERNEL ) ;
if ( ! smi )
return - ENOMEM ;
platform_set_drvdata ( pdev , smi ) ;
2022-01-21 17:24:29 +00:00
switch ( node - > bus_type ) {
case SMI_I2C :
2022-07-10 00:16:49 +03:00
return smi_i2c_probe ( pdev , smi , node - > instances ) ;
2022-01-21 17:24:29 +00:00
case SMI_SPI :
2022-07-10 00:16:49 +03:00
return smi_spi_probe ( pdev , smi , node - > instances ) ;
2022-01-21 17:24:29 +00:00
case SMI_AUTO_DETECT :
2022-07-10 00:16:48 +03:00
/*
* For backwards - compatibility with the existing nodes I2C
* is checked first and if such entries are found ONLY I2C
* devices are created . Since some existing nodes that were
* already handled by this driver could also contain unrelated
* SpiSerialBus nodes that were previously ignored , and this
* preserves that behavior .
*/
2022-07-10 00:16:49 +03:00
ret = smi_i2c_probe ( pdev , smi , node - > instances ) ;
2022-07-10 00:16:48 +03:00
if ( ret ! = - ENOENT )
return ret ;
2022-07-10 00:16:49 +03:00
return smi_spi_probe ( pdev , smi , node - > instances ) ;
2022-01-21 17:24:29 +00:00
default :
return - EINVAL ;
}
2022-01-21 17:24:28 +00:00
}
2023-03-02 15:47:30 +01:00
static void smi_remove ( struct platform_device * pdev )
2018-08-09 13:40:46 +02:00
{
2022-01-21 17:24:27 +00:00
struct smi * smi = platform_get_drvdata ( pdev ) ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:28 +00:00
smi_devs_unregister ( smi ) ;
2018-08-09 13:40:46 +02:00
}
2022-01-21 17:24:29 +00:00
static const struct smi_node bsg1160_data = {
. instances = {
{ " bmc150_accel " , IRQ_RESOURCE_GPIO , 0 } ,
{ " bmc150_magn " } ,
{ " bmg160 " } ,
{ }
} ,
. bus_type = SMI_I2C ,
2018-08-09 13:40:46 +02:00
} ;
2022-01-21 17:24:29 +00:00
static const struct smi_node bsg2150_data = {
. instances = {
{ " bmc150_accel " , IRQ_RESOURCE_GPIO , 0 } ,
{ " bmc150_magn " } ,
/* The resources describe a 3th client, but it is not really there. */
{ " bsg2150_dummy_dev " } ,
{ }
} ,
. bus_type = SMI_I2C ,
2018-12-20 15:34:51 +01:00
} ;
2022-01-21 17:24:29 +00:00
static const struct smi_node int3515_data = {
. instances = {
{ " tps6598x " , IRQ_RESOURCE_APIC , 0 } ,
{ " tps6598x " , IRQ_RESOURCE_APIC , 1 } ,
{ " tps6598x " , IRQ_RESOURCE_APIC , 2 } ,
{ " tps6598x " , IRQ_RESOURCE_APIC , 3 } ,
{ }
} ,
. bus_type = SMI_I2C ,
2021-10-20 07:56:20 +05:30
} ;
2018-11-28 13:45:34 +02:00
2022-01-21 17:24:31 +00:00
static const struct smi_node cs35l41_hda = {
. instances = {
{ " cs35l41-hda " , IRQ_RESOURCE_GPIO , 0 } ,
{ " cs35l41-hda " , IRQ_RESOURCE_GPIO , 0 } ,
{ " cs35l41-hda " , IRQ_RESOURCE_GPIO , 0 } ,
{ " cs35l41-hda " , IRQ_RESOURCE_GPIO , 0 } ,
{ }
} ,
. bus_type = SMI_AUTO_DETECT ,
} ;
2018-08-09 13:40:46 +02:00
/*
2022-01-21 17:24:27 +00:00
* Note new device - ids must also be added to ignore_serial_bus_ids in
2018-08-09 13:40:46 +02:00
* drivers / acpi / scan . c : acpi_device_enumeration_by_parent ( ) .
*/
2022-01-21 17:24:27 +00:00
static const struct acpi_device_id smi_acpi_ids [ ] = {
2022-01-21 17:24:29 +00:00
{ " BSG1160 " , ( unsigned long ) & bsg1160_data } ,
{ " BSG2150 " , ( unsigned long ) & bsg2150_data } ,
2022-01-21 17:24:31 +00:00
{ " CSC3551 " , ( unsigned long ) & cs35l41_hda } ,
2022-07-10 00:16:53 +03:00
{ " INT3515 " , ( unsigned long ) & int3515_data } ,
2022-01-21 17:24:31 +00:00
/* Non-conforming _HID for Cirrus Logic already released */
{ " CLSA0100 " , ( unsigned long ) & cs35l41_hda } ,
2022-07-27 10:59:24 +01:00
{ " CLSA0101 " , ( unsigned long ) & cs35l41_hda } ,
2018-08-09 13:40:46 +02:00
{ }
} ;
2022-01-21 17:24:27 +00:00
MODULE_DEVICE_TABLE ( acpi , smi_acpi_ids ) ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:27 +00:00
static struct platform_driver smi_driver = {
2018-08-09 13:40:46 +02:00
. driver = {
2022-01-21 17:24:27 +00:00
. name = " Serial bus multi instantiate pseudo device driver " ,
. acpi_match_table = smi_acpi_ids ,
2018-08-09 13:40:46 +02:00
} ,
2022-01-21 17:24:27 +00:00
. probe = smi_probe ,
2023-03-02 15:47:30 +01:00
. remove_new = smi_remove ,
2018-08-09 13:40:46 +02:00
} ;
2022-01-21 17:24:27 +00:00
module_platform_driver ( smi_driver ) ;
2018-08-09 13:40:46 +02:00
2022-01-21 17:24:27 +00:00
MODULE_DESCRIPTION ( " Serial multi instantiate pseudo device driver " ) ;
2018-08-09 13:40:46 +02:00
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;