2018-08-09 13:40:46 +02:00
// SPDX-License-Identifier: GPL-2.0+
/*
* I2C multi - instantiate driver , pseudo driver to instantiate multiple
* i2c - clients from a single fwnode .
*
* 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>
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
2018-08-09 13:40:46 +02:00
struct i2c_inst_data {
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
} ;
struct i2c_multi_inst_data {
int num_clients ;
2020-02-12 18:55:25 -06:00
struct i2c_client * clients [ ] ;
2018-08-09 13:40:46 +02:00
} ;
static int i2c_multi_inst_probe ( struct platform_device * pdev )
{
struct i2c_multi_inst_data * multi ;
const struct i2c_inst_data * inst_data ;
struct i2c_board_info board_info = { } ;
struct device * dev = & pdev - > dev ;
struct acpi_device * adev ;
char name [ 32 ] ;
int i , ret ;
2020-11-05 13:05:30 +02:00
inst_data = device_get_match_data ( dev ) ;
if ( ! inst_data ) {
2018-08-09 13:40:46 +02:00
dev_err ( dev , " Error ACPI match data is missing \n " ) ;
return - ENODEV ;
}
adev = ACPI_COMPANION ( dev ) ;
/* Count number of clients to instantiate */
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 ;
2018-08-09 13:40:46 +02:00
2019-07-25 22:05:50 +03:00
multi = devm_kmalloc ( dev , struct_size ( multi , clients , ret ) , GFP_KERNEL ) ;
2018-08-09 13:40:46 +02:00
if ( ! multi )
return - ENOMEM ;
2018-11-28 13:45:30 +02:00
multi - > num_clients = ret ;
2018-08-09 13:40:46 +02:00
2018-11-28 13:45:30 +02:00
for ( i = 0 ; i < multi - > num_clients & & inst_data [ i ] . type ; i + + ) {
2018-08-09 13:40:46 +02:00
memset ( & board_info , 0 , sizeof ( board_info ) ) ;
strlcpy ( board_info . type , inst_data [ i ] . type , I2C_NAME_SIZE ) ;
2019-09-20 13:02:33 +03:00
snprintf ( name , sizeof ( name ) , " %s-%s.%d " , dev_name ( dev ) ,
2018-11-28 13:45:33 +02:00
inst_data [ i ] . type , i ) ;
2018-08-09 13:40:46 +02:00
board_info . dev_name = name ;
2018-11-28 13:45:31 +02:00
switch ( inst_data [ i ] . flags & IRQ_RESOURCE_TYPE ) {
case IRQ_RESOURCE_GPIO :
ret = acpi_dev_gpio_irq_get ( adev , inst_data [ i ] . irq_idx ) ;
2018-08-09 13:40:46 +02:00
if ( ret < 0 ) {
dev_err ( dev , " Error requesting irq at index %d: %d \n " ,
2018-11-28 13:45:31 +02:00
inst_data [ i ] . irq_idx , ret ) ;
2018-08-09 13:40:46 +02:00
goto error ;
}
board_info . irq = ret ;
2018-11-28 13:45:31 +02:00
break ;
2018-11-28 13:45:32 +02:00
case IRQ_RESOURCE_APIC :
ret = platform_get_irq ( pdev , inst_data [ i ] . irq_idx ) ;
if ( ret < 0 ) {
dev_dbg ( dev , " Error requesting irq at index %d: %d \n " ,
inst_data [ i ] . irq_idx , ret ) ;
2019-10-11 13:22:58 +03:00
goto error ;
2018-11-28 13:45:32 +02:00
}
board_info . irq = ret ;
break ;
2018-11-28 13:45:31 +02:00
default :
board_info . irq = 0 ;
break ;
2018-08-09 13:40:46 +02:00
}
multi - > clients [ i ] = i2c_acpi_new_device ( dev , i , & board_info ) ;
2018-11-28 13:45:27 +02:00
if ( IS_ERR ( multi - > clients [ i ] ) ) {
2020-11-05 13:05:28 +02:00
ret = dev_err_probe ( dev , PTR_ERR ( multi - > clients [ i ] ) ,
" Error creating i2c-client, idx %d \n " , i ) ;
2018-08-09 13:40:46 +02:00
goto error ;
}
}
2018-11-28 13:45:30 +02:00
if ( i < multi - > num_clients ) {
dev_err ( dev , " Error finding driver, idx %d \n " , i ) ;
ret = - ENODEV ;
goto error ;
}
2018-08-09 13:40:46 +02:00
platform_set_drvdata ( pdev , multi ) ;
return 0 ;
error :
while ( - - i > = 0 )
i2c_unregister_device ( multi - > clients [ i ] ) ;
return ret ;
}
static int i2c_multi_inst_remove ( struct platform_device * pdev )
{
struct i2c_multi_inst_data * multi = platform_get_drvdata ( pdev ) ;
int i ;
for ( i = 0 ; i < multi - > num_clients ; i + + )
i2c_unregister_device ( multi - > clients [ i ] ) ;
return 0 ;
}
static const struct i2c_inst_data bsg1160_data [ ] = {
2018-11-28 13:45:31 +02:00
{ " bmc150_accel " , IRQ_RESOURCE_GPIO , 0 } ,
{ " bmc150_magn " } ,
{ " bmg160 " } ,
2018-08-09 13:40:46 +02:00
{ }
} ;
2018-12-20 15:34:51 +01:00
static const struct i2c_inst_data bsg2150_data [ ] = {
{ " bmc150_accel " , IRQ_RESOURCE_GPIO , 0 } ,
{ " bmc150_magn " } ,
/* The resources describe a 3th client, but it is not really there. */
{ " bsg2150_dummy_dev " } ,
{ }
} ;
2020-12-23 17:36:44 +03:00
/*
* Device with _HID INT3515 ( TI PD controllers ) has some unresolved interrupt
* issues . The most common problem seen is interrupt flood .
*
* There are at least two known causes . Firstly , on some boards , the
* I2CSerialBus resource index does not match the Interrupt resource , i . e . they
* are not one - to - one mapped like in the array below . Secondly , on some boards
* the IRQ line from the PD controller is not actually connected at all . But the
* interrupt flood is also seen on some boards where those are not a problem , so
* there are some other problems as well .
*
* Because of the issues with the interrupt , the device is disabled for now . If
* you wish to debug the issues , uncomment the below , and add an entry for the
* INT3515 device to the i2c_multi_instance_ids table .
*
* static const struct i2c_inst_data int3515_data [ ] = {
* { " tps6598x " , IRQ_RESOURCE_APIC , 0 } ,
* { " tps6598x " , IRQ_RESOURCE_APIC , 1 } ,
* { " tps6598x " , IRQ_RESOURCE_APIC , 2 } ,
* { " tps6598x " , IRQ_RESOURCE_APIC , 3 } ,
* { }
* } ;
*/
2018-11-28 13:45:34 +02:00
2018-08-09 13:40:46 +02:00
/*
* Note new device - ids must also be added to i2c_multi_instantiate_ids in
* drivers / acpi / scan . c : acpi_device_enumeration_by_parent ( ) .
*/
static const struct acpi_device_id i2c_multi_inst_acpi_ids [ ] = {
{ " BSG1160 " , ( unsigned long ) bsg1160_data } ,
2018-12-20 15:34:51 +01:00
{ " BSG2150 " , ( unsigned long ) bsg2150_data } ,
2018-08-09 13:40:46 +02:00
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , i2c_multi_inst_acpi_ids ) ;
static struct platform_driver i2c_multi_inst_driver = {
. driver = {
. name = " I2C multi instantiate pseudo device driver " ,
2020-11-05 13:05:27 +02:00
. acpi_match_table = i2c_multi_inst_acpi_ids ,
2018-08-09 13:40:46 +02:00
} ,
. probe = i2c_multi_inst_probe ,
. remove = i2c_multi_inst_remove ,
} ;
module_platform_driver ( i2c_multi_inst_driver ) ;
MODULE_DESCRIPTION ( " I2C multi instantiate pseudo device driver " ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;