diff mbox series

[v4,4/6] platform/x86: dell-smo8800: Move instantiation of lis3lv02d i2c_client from i2c-i801 to dell-lis3lv02d

Message ID 20240624111519.15652-5-hdegoede@redhat.com (mailing list archive)
State Changes Requested, archived
Headers show
Series i2c-i801 / dell-lis3lv02d: Move instantiation of lis3lv02d i2c_client from i2c-i801 to dell-lis3lv02d | expand

Commit Message

Hans de Goede June 24, 2024, 11:15 a.m. UTC
Various Dell laptops have an lis3lv02d freefall/accelerometer sensor.
The lis3lv02d chip has an interrupt line as well as an I2C connection to
the system's main SMBus.

The lis3lv02d is described in the ACPI tables by an SMO88xx ACPI device,
but the SMO88xx ACPI fwnodes are incomplete and only list an IRQ resource.

So far this has been worked around with some SMO88xx specific quirk code
in the generic i2c-i801 driver, but it is not necessary to handle the Dell
specific instantiation of i2c_client-s for SMO88xx ACPI devices there.

The kernel already instantiates platform_device-s for these with an
acpi:SMO88xx modalias. The drivers/platform/x86/dell/dell-smo8800.c
driver binds to this platform device but this only deals with
the interrupt resource. Add a drivers/platform/x86/dell/dell-lis3lv02d.c
which will matches on the same acpi:SMO88xx modaliases and move
the i2c_client instantiation from the generic i2c-i801 driver there.

Moving the i2c_client instantiation has the following advantages:

1. This moves the SMO88xx ACPI device quirk handling away from the generic
i2c-i801 module which is loaded on all Intel x86 machines to a module
which will only be loaded when there is an ACPI SMO88xx device.

2. This removes the duplication of the SMO88xx ACPI Hardware ID (HID) table
between the i2c-i801 and dell-smo8800 drivers.

3. This allows extending the quirk handling by adding new code and related
module parameters to the dell-lis3lv02d driver, without needing to modify
the i2c-i801 code.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
- Move the i2c_client instantiation to a new dell-lis3lv02d driver instead
  of adding it to the dell-smo8800 driver

Changes in v3:
- Use an i2c bus notifier so that the i2c_client will still be instantiated if
  the i801 i2c_adapter shows up later or is re-probed (removed + added again)
- Switch to standard dmi_system_id matching to check both sys-vendor +
  product-name DMI fields
- Use unique i2c_adapter->name prefix for primary i2c_801 controller
  to avoid needing to duplicate PCI ids for extra IDF i2c_801 i2c_adapter-s
- Drop MODULE_SOFTDEP("pre: i2c-i801"), this is now no longer necessary
- Rebase on Torvalds master for recent additions of extra models in
  the dell_lis3lv02d_devices[] list

Changes in v2:
- Use a pci_device_id table to check for IDF (non main) i2c-i801 SMBusses
- Add a comment documenting the IDF PCI device ids
---
 drivers/i2c/busses/i2c-i801.c              | 124 -------------
 drivers/platform/x86/dell/Makefile         |   1 +
 drivers/platform/x86/dell/dell-lis3lv02d.c | 199 +++++++++++++++++++++
 3 files changed, 200 insertions(+), 124 deletions(-)
 create mode 100644 drivers/platform/x86/dell/dell-lis3lv02d.c

Comments

Pali Rohár June 24, 2024, 6:14 p.m. UTC | #1
On Monday 24 June 2024 13:15:16 Hans de Goede wrote:
> +static int match_acpi_device_ids(struct device *dev, const void *data)
> +{

You can mark this function as __init as it is called only from
dell_lis3lv02d_init to free space.

> +	const struct acpi_device_id *ids = data;
> +
> +	return acpi_match_device(ids, dev) ? 1 : 0;
> +}
> +
> +static int __init dell_lis3lv02d_init(void)
> +{
> +	struct device *dev;
> +	int err;
> +
> +	/*
> +	 * First check for a matching platform_device. This protects against
> +	 * SMO88xx ACPI fwnodes which actually do have an I2C resource, which
> +	 * will already have an i2c_client instantiated (not a platform_device).
> +	 */
> +	dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids);
> +	if (!dev) {
> +		pr_debug("No SMO88xx platform-device found\n");
> +		return 0;

Is zero return value expected? Should not be it something like -ENODEV?

> +	}
> +	put_device(dev);
> +
> +	lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices);
> +	if (!lis3lv02d_dmi_id) {

You can cache the value lis3lv02d_dmi_id->driver_data instead of caching
lis3lv02d_dmi_id pointer and then you can mark lis3lv02d_devices array
as __initconst to free additional space not needed at runtime on x86
machines without accelerometer where CONFIG_DELL_SMO8800=y.

> +		pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
> +		return 0;
> +	}
> +
> +	/*
> +	 * Register i2c-bus notifier + queue initial scan for lis3lv02d
> +	 * i2c_client instantiation.
> +	 */
> +	err = bus_register_notifier(&i2c_bus_type, &i2c_nb);
> +	if (err)
> +		return err;
> +
> +	notifier_registered = true;
> +
> +	queue_work(system_long_wq, &i2c_work);
> +	return 0;
> +}
> +module_init(dell_lis3lv02d_init);
> +
> +static void __exit dell_lis3lv02d_module_exit(void)
> +{
> +	if (!notifier_registered)
> +		return;
> +
> +	bus_unregister_notifier(&i2c_bus_type, &i2c_nb);
> +	cancel_work_sync(&i2c_work);
> +	i2c_unregister_device(i2c_dev);
> +}
> +module_exit(dell_lis3lv02d_module_exit);
> +
> +MODULE_DESCRIPTION("lis3lv02d i2c-client instantiation for ACPI SMO88xx devices");
> +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.45.1
>
kernel test robot June 28, 2024, 12:01 a.m. UTC | #2
Hi Hans,

kernel test robot noticed the following build errors:

[auto build test ERROR on andi-shyti/i2c/i2c-host]
[also build test ERROR on wsa/i2c/for-next linus/master v6.10-rc5 next-20240627]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Hans-de-Goede/i2c-core-Setup-i2c_adapter-runtime-pm-before-calling-device_add/20240626-053449
base:   git://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c/i2c-host
patch link:    https://lore.kernel.org/r/20240624111519.15652-5-hdegoede%40redhat.com
patch subject: [PATCH v4 4/6] platform/x86: dell-smo8800: Move instantiation of lis3lv02d i2c_client from i2c-i801 to dell-lis3lv02d
config: i386-randconfig-002-20240628 (https://download.01.org/0day-ci/archive/20240628/202406280739.e0s764jH-lkp@intel.com/config)
compiler: gcc-13 (Ubuntu 13.2.0-4ubuntu3) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240628/202406280739.e0s764jH-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202406280739.e0s764jH-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   drivers/platform/x86/dell/dell-lis3lv02d.c: In function 'find_i801':
>> drivers/platform/x86/dell/dell-lis3lv02d.c:77:21: error: implicit declaration of function 'i2c_get_adapter'; did you mean 'i2c_get_adapdata'? [-Werror=implicit-function-declaration]
      77 |         *adap_ret = i2c_get_adapter(adap->nr);
         |                     ^~~~~~~~~~~~~~~
         |                     i2c_get_adapdata
>> drivers/platform/x86/dell/dell-lis3lv02d.c:77:19: warning: assignment to 'struct i2c_adapter *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
      77 |         *adap_ret = i2c_get_adapter(adap->nr);
         |                   ^
   drivers/platform/x86/dell/dell-lis3lv02d.c: In function 'instantiate_i2c_client':
>> drivers/platform/x86/dell/dell-lis3lv02d.c:96:19: error: implicit declaration of function 'i2c_new_client_device' [-Werror=implicit-function-declaration]
      96 |         i2c_dev = i2c_new_client_device(adap, &info);
         |                   ^~~~~~~~~~~~~~~~~~~~~
>> drivers/platform/x86/dell/dell-lis3lv02d.c:96:17: warning: assignment to 'struct i2c_client *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
      96 |         i2c_dev = i2c_new_client_device(adap, &info);
         |                 ^
>> drivers/platform/x86/dell/dell-lis3lv02d.c:104:9: error: implicit declaration of function 'i2c_put_adapter' [-Werror=implicit-function-declaration]
     104 |         i2c_put_adapter(adap);
         |         ^~~~~~~~~~~~~~~
   drivers/platform/x86/dell/dell-lis3lv02d.c: In function 'dell_lis3lv02d_module_exit':
>> drivers/platform/x86/dell/dell-lis3lv02d.c:193:9: error: implicit declaration of function 'i2c_unregister_device' [-Werror=implicit-function-declaration]
     193 |         i2c_unregister_device(i2c_dev);
         |         ^~~~~~~~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors


vim +77 drivers/platform/x86/dell/dell-lis3lv02d.c

    65	
    66	static int find_i801(struct device *dev, void *data)
    67	{
    68		struct i2c_adapter *adap, **adap_ret = data;
    69	
    70		adap = i2c_verify_adapter(dev);
    71		if (!adap)
    72			return 0;
    73	
    74		if (!i2c_adapter_is_main_i801(adap))
    75			return 0;
    76	
  > 77		*adap_ret = i2c_get_adapter(adap->nr);
    78		return 1;
    79	}
    80	
    81	static void instantiate_i2c_client(struct work_struct *work)
    82	{
    83		struct i2c_board_info info = { };
    84		struct i2c_adapter *adap = NULL;
    85	
    86		if (i2c_dev)
    87			return;
    88	
    89		bus_for_each_dev(&i2c_bus_type, NULL, &adap, find_i801);
    90		if (!adap)
    91			return;
    92	
    93		info.addr = (long)lis3lv02d_dmi_id->driver_data;
    94		strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
    95	
  > 96		i2c_dev = i2c_new_client_device(adap, &info);
    97		if (IS_ERR(i2c_dev)) {
    98			pr_err("error %ld registering i2c_client\n", PTR_ERR(i2c_dev));
    99			i2c_dev = NULL;
   100		} else {
   101			pr_debug("registered lis3lv02d on address 0x%02x\n", info.addr);
   102		}
   103	
 > 104		i2c_put_adapter(adap);
   105	}
   106	static DECLARE_WORK(i2c_work, instantiate_i2c_client);
   107	
   108	static int i2c_bus_notify(struct notifier_block *nb, unsigned long action, void *data)
   109	{
   110		struct device *dev = data;
   111		struct i2c_client *client;
   112		struct i2c_adapter *adap;
   113	
   114		switch (action) {
   115		case BUS_NOTIFY_ADD_DEVICE:
   116			adap = i2c_verify_adapter(dev);
   117			if (!adap)
   118				break;
   119	
   120			if (i2c_adapter_is_main_i801(adap))
   121				queue_work(system_long_wq, &i2c_work);
   122			break;
   123		case BUS_NOTIFY_REMOVED_DEVICE:
   124			client = i2c_verify_client(dev);
   125			if (!client)
   126				break;
   127	
   128			if (i2c_dev == client) {
   129				pr_debug("lis3lv02d i2c_client removed\n");
   130				i2c_dev = NULL;
   131			}
   132			break;
   133		default:
   134			break;
   135		}
   136	
   137		return 0;
   138	}
   139	static struct notifier_block i2c_nb = { .notifier_call = i2c_bus_notify };
   140	
   141	static int match_acpi_device_ids(struct device *dev, const void *data)
   142	{
   143		const struct acpi_device_id *ids = data;
   144	
   145		return acpi_match_device(ids, dev) ? 1 : 0;
   146	}
   147	
   148	static int __init dell_lis3lv02d_init(void)
   149	{
   150		struct device *dev;
   151		int err;
   152	
   153		/*
   154		 * First check for a matching platform_device. This protects against
   155		 * SMO88xx ACPI fwnodes which actually do have an I2C resource, which
   156		 * will already have an i2c_client instantiated (not a platform_device).
   157		 */
   158		dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids);
   159		if (!dev) {
   160			pr_debug("No SMO88xx platform-device found\n");
   161			return 0;
   162		}
   163		put_device(dev);
   164	
   165		lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices);
   166		if (!lis3lv02d_dmi_id) {
   167			pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
   168			return 0;
   169		}
   170	
   171		/*
   172		 * Register i2c-bus notifier + queue initial scan for lis3lv02d
   173		 * i2c_client instantiation.
   174		 */
   175		err = bus_register_notifier(&i2c_bus_type, &i2c_nb);
   176		if (err)
   177			return err;
   178	
   179		notifier_registered = true;
   180	
   181		queue_work(system_long_wq, &i2c_work);
   182		return 0;
   183	}
   184	module_init(dell_lis3lv02d_init);
   185	
   186	static void __exit dell_lis3lv02d_module_exit(void)
   187	{
   188		if (!notifier_registered)
   189			return;
   190	
   191		bus_unregister_notifier(&i2c_bus_type, &i2c_nb);
   192		cancel_work_sync(&i2c_work);
 > 193		i2c_unregister_device(i2c_dev);
   194	}
   195	module_exit(dell_lis3lv02d_module_exit);
   196
Hans de Goede July 2, 2024, 6:54 p.m. UTC | #3
Hi,

On 6/24/24 8:14 PM, Pali Rohár wrote:
> On Monday 24 June 2024 13:15:16 Hans de Goede wrote:
>> +static int match_acpi_device_ids(struct device *dev, const void *data)
>> +{
> 
> You can mark this function as __init as it is called only from
> dell_lis3lv02d_init to free space.
> 
>> +	const struct acpi_device_id *ids = data;
>> +
>> +	return acpi_match_device(ids, dev) ? 1 : 0;
>> +}
>> +
>> +static int __init dell_lis3lv02d_init(void)
>> +{
>> +	struct device *dev;
>> +	int err;
>> +
>> +	/*
>> +	 * First check for a matching platform_device. This protects against
>> +	 * SMO88xx ACPI fwnodes which actually do have an I2C resource, which
>> +	 * will already have an i2c_client instantiated (not a platform_device).
>> +	 */
>> +	dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids);
>> +	if (!dev) {
>> +		pr_debug("No SMO88xx platform-device found\n");
>> +		return 0;
> 
> Is zero return value expected? Should not be it something like -ENODEV?

This is a module_init() function returning non 0 leads to an error getting
logged and the modprobe command to return a non 0 value which is not what
we want.

>> +	}
>> +	put_device(dev);
>> +
>> +	lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices);
>> +	if (!lis3lv02d_dmi_id) {
> 
> You can cache the value lis3lv02d_dmi_id->driver_data instead of caching
> lis3lv02d_dmi_id pointer and then you can mark lis3lv02d_devices array
> as __initconst to free additional space not needed at runtime on x86
> machines without accelerometer where CONFIG_DELL_SMO8800=y.

This is a good idea, done for v5.

Regards,

Hans


> 
>> +		pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
>> +		return 0;
>> +	}
>> +
>> +	/*
>> +	 * Register i2c-bus notifier + queue initial scan for lis3lv02d
>> +	 * i2c_client instantiation.
>> +	 */
>> +	err = bus_register_notifier(&i2c_bus_type, &i2c_nb);
>> +	if (err)
>> +		return err;
>> +
>> +	notifier_registered = true;
>> +
>> +	queue_work(system_long_wq, &i2c_work);
>> +	return 0;
>> +}
>> +module_init(dell_lis3lv02d_init);
>> +
>> +static void __exit dell_lis3lv02d_module_exit(void)
>> +{
>> +	if (!notifier_registered)
>> +		return;
>> +
>> +	bus_unregister_notifier(&i2c_bus_type, &i2c_nb);
>> +	cancel_work_sync(&i2c_work);
>> +	i2c_unregister_device(i2c_dev);
>> +}
>> +module_exit(dell_lis3lv02d_module_exit);
>> +
>> +MODULE_DESCRIPTION("lis3lv02d i2c-client instantiation for ACPI SMO88xx devices");
>> +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
>> +MODULE_LICENSE("GPL");
>> -- 
>> 2.45.1
>>
>
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
index 94265ee300c0..375781079e0d 100644
--- a/drivers/i2c/busses/i2c-i801.c
+++ b/drivers/i2c/busses/i2c-i801.c
@@ -1153,127 +1153,6 @@  static void dmi_check_onboard_devices(const struct dmi_header *dm, void *adap)
 	}
 }
 
-/* NOTE: Keep this list in sync with drivers/platform/x86/dell-smo8800.c */
-static const char *const acpi_smo8800_ids[] = {
-	"SMO8800",
-	"SMO8801",
-	"SMO8810",
-	"SMO8811",
-	"SMO8820",
-	"SMO8821",
-	"SMO8830",
-	"SMO8831",
-};
-
-static acpi_status check_acpi_smo88xx_device(acpi_handle obj_handle,
-					     u32 nesting_level,
-					     void *context,
-					     void **return_value)
-{
-	struct acpi_device_info *info;
-	acpi_status status;
-	char *hid;
-	int i;
-
-	status = acpi_get_object_info(obj_handle, &info);
-	if (ACPI_FAILURE(status))
-		return AE_OK;
-
-	if (!(info->valid & ACPI_VALID_HID))
-		goto smo88xx_not_found;
-
-	hid = info->hardware_id.string;
-	if (!hid)
-		goto smo88xx_not_found;
-
-	i = match_string(acpi_smo8800_ids, ARRAY_SIZE(acpi_smo8800_ids), hid);
-	if (i < 0)
-		goto smo88xx_not_found;
-
-	kfree(info);
-
-	*return_value = NULL;
-	return AE_CTRL_TERMINATE;
-
-smo88xx_not_found:
-	kfree(info);
-	return AE_OK;
-}
-
-static bool is_dell_system_with_lis3lv02d(void)
-{
-	void *err = ERR_PTR(-ENOENT);
-
-	if (!dmi_match(DMI_SYS_VENDOR, "Dell Inc."))
-		return false;
-
-	/*
-	 * Check that ACPI device SMO88xx is present and is functioning.
-	 * Function acpi_get_devices() already filters all ACPI devices
-	 * which are not present or are not functioning.
-	 * ACPI device SMO88xx represents our ST microelectronics lis3lv02d
-	 * accelerometer but unfortunately ACPI does not provide any other
-	 * information (like I2C address).
-	 */
-	acpi_get_devices(NULL, check_acpi_smo88xx_device, NULL, &err);
-
-	return !IS_ERR(err);
-}
-
-/*
- * Accelerometer's I2C address is not specified in DMI nor ACPI,
- * so it is needed to define mapping table based on DMI product names.
- */
-static const struct {
-	const char *dmi_product_name;
-	unsigned short i2c_addr;
-} dell_lis3lv02d_devices[] = {
-	/*
-	 * Dell platform team told us that these Latitude devices have
-	 * ST microelectronics accelerometer at I2C address 0x29.
-	 */
-	{ "Latitude E5250",     0x29 },
-	{ "Latitude E5450",     0x29 },
-	{ "Latitude E5550",     0x29 },
-	{ "Latitude E6440",     0x29 },
-	{ "Latitude E6440 ATG", 0x29 },
-	{ "Latitude E6540",     0x29 },
-	/*
-	 * Additional individual entries were added after verification.
-	 */
-	{ "Latitude 5480",      0x29 },
-	{ "Precision 3540",     0x29 },
-	{ "Vostro V131",        0x1d },
-	{ "Vostro 5568",        0x29 },
-	{ "XPS 15 7590",        0x29 },
-};
-
-static void register_dell_lis3lv02d_i2c_device(struct i801_priv *priv)
-{
-	struct i2c_board_info info;
-	const char *dmi_product_name;
-	int i;
-
-	dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
-	for (i = 0; i < ARRAY_SIZE(dell_lis3lv02d_devices); ++i) {
-		if (strcmp(dmi_product_name,
-			   dell_lis3lv02d_devices[i].dmi_product_name) == 0)
-			break;
-	}
-
-	if (i == ARRAY_SIZE(dell_lis3lv02d_devices)) {
-		dev_warn(&priv->pci_dev->dev,
-			 "Accelerometer lis3lv02d is present on SMBus but its"
-			 " address is unknown, skipping registration\n");
-		return;
-	}
-
-	memset(&info, 0, sizeof(struct i2c_board_info));
-	info.addr = dell_lis3lv02d_devices[i].i2c_addr;
-	strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
-	i2c_new_client_device(&priv->adapter, &info);
-}
-
 /* Register optional slaves */
 static void i801_probe_optional_slaves(struct i801_priv *priv)
 {
@@ -1293,9 +1172,6 @@  static void i801_probe_optional_slaves(struct i801_priv *priv)
 	if (dmi_name_in_vendors("FUJITSU"))
 		dmi_walk(dmi_check_onboard_devices, &priv->adapter);
 
-	if (is_dell_system_with_lis3lv02d())
-		register_dell_lis3lv02d_i2c_device(priv);
-
 	/* Instantiate SPD EEPROMs unless the SMBus is multiplexed */
 #ifdef CONFIG_I2C_I801_MUX
 	if (!priv->mux_pdev)
diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile
index 8176a257d9c3..970409c107b0 100644
--- a/drivers/platform/x86/dell/Makefile
+++ b/drivers/platform/x86/dell/Makefile
@@ -14,6 +14,7 @@  dell-smbios-objs			:= dell-smbios-base.o
 dell-smbios-$(CONFIG_DELL_SMBIOS_WMI)	+= dell-smbios-wmi.o
 dell-smbios-$(CONFIG_DELL_SMBIOS_SMM)	+= dell-smbios-smm.o
 obj-$(CONFIG_DELL_SMO8800)		+= dell-smo8800.o
+obj-$(CONFIG_DELL_SMO8800)		+= dell-lis3lv02d.o
 obj-$(CONFIG_DELL_UART_BACKLIGHT)	+= dell-uart-backlight.o
 obj-$(CONFIG_DELL_WMI)			+= dell-wmi.o
 dell-wmi-objs				:= dell-wmi-base.o
diff --git a/drivers/platform/x86/dell/dell-lis3lv02d.c b/drivers/platform/x86/dell/dell-lis3lv02d.c
new file mode 100644
index 000000000000..e581b8e2a603
--- /dev/null
+++ b/drivers/platform/x86/dell/dell-lis3lv02d.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * lis3lv02d i2c-client instantiation for ACPI SMO88xx devices without I2C resources.
+ *
+ *  Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device/bus.h>
+#include <linux/dmi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include "dell-smo8800-ids.h"
+
+#define DELL_LIS3LV02D_DMI_ENTRY(product_name, i2c_addr)                 \
+	{                                                                \
+		.matches = {                                             \
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),    \
+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, product_name), \
+		},                                                       \
+		.driver_data = (void *)(uintptr_t)(i2c_addr),            \
+	}
+
+/*
+ * Accelerometer's I2C address is not specified in DMI nor ACPI,
+ * so it is needed to define mapping table based on DMI product names.
+ */
+static const struct dmi_system_id lis3lv02d_devices[] = {
+	/*
+	 * Dell platform team told us that these Latitude devices have
+	 * ST microelectronics accelerometer at I2C address 0x29.
+	 */
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E5250",     0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E5450",     0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E5550",     0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440",     0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440 ATG", 0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude E6540",     0x29),
+	/*
+	 * Additional individual entries were added after verification.
+	 */
+	DELL_LIS3LV02D_DMI_ENTRY("Latitude 5480",      0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Precision 3540",     0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("Vostro V131",        0x1d),
+	DELL_LIS3LV02D_DMI_ENTRY("Vostro 5568",        0x29),
+	DELL_LIS3LV02D_DMI_ENTRY("XPS 15 7590",        0x29),
+	{ }
+};
+
+static const struct dmi_system_id *lis3lv02d_dmi_id;
+static struct i2c_client *i2c_dev;
+static bool notifier_registered;
+
+static bool i2c_adapter_is_main_i801(struct i2c_adapter *adap)
+{
+	/*
+	 * Only match the main I801 adapter and reject secondary adapters
+	 * which names start with "SMBus I801 IDF adapter".
+	 */
+	return strstarts(adap->name, "SMBus I801 adapter");
+}
+
+static int find_i801(struct device *dev, void *data)
+{
+	struct i2c_adapter *adap, **adap_ret = data;
+
+	adap = i2c_verify_adapter(dev);
+	if (!adap)
+		return 0;
+
+	if (!i2c_adapter_is_main_i801(adap))
+		return 0;
+
+	*adap_ret = i2c_get_adapter(adap->nr);
+	return 1;
+}
+
+static void instantiate_i2c_client(struct work_struct *work)
+{
+	struct i2c_board_info info = { };
+	struct i2c_adapter *adap = NULL;
+
+	if (i2c_dev)
+		return;
+
+	bus_for_each_dev(&i2c_bus_type, NULL, &adap, find_i801);
+	if (!adap)
+		return;
+
+	info.addr = (long)lis3lv02d_dmi_id->driver_data;
+	strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
+
+	i2c_dev = i2c_new_client_device(adap, &info);
+	if (IS_ERR(i2c_dev)) {
+		pr_err("error %ld registering i2c_client\n", PTR_ERR(i2c_dev));
+		i2c_dev = NULL;
+	} else {
+		pr_debug("registered lis3lv02d on address 0x%02x\n", info.addr);
+	}
+
+	i2c_put_adapter(adap);
+}
+static DECLARE_WORK(i2c_work, instantiate_i2c_client);
+
+static int i2c_bus_notify(struct notifier_block *nb, unsigned long action, void *data)
+{
+	struct device *dev = data;
+	struct i2c_client *client;
+	struct i2c_adapter *adap;
+
+	switch (action) {
+	case BUS_NOTIFY_ADD_DEVICE:
+		adap = i2c_verify_adapter(dev);
+		if (!adap)
+			break;
+
+		if (i2c_adapter_is_main_i801(adap))
+			queue_work(system_long_wq, &i2c_work);
+		break;
+	case BUS_NOTIFY_REMOVED_DEVICE:
+		client = i2c_verify_client(dev);
+		if (!client)
+			break;
+
+		if (i2c_dev == client) {
+			pr_debug("lis3lv02d i2c_client removed\n");
+			i2c_dev = NULL;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+static struct notifier_block i2c_nb = { .notifier_call = i2c_bus_notify };
+
+static int match_acpi_device_ids(struct device *dev, const void *data)
+{
+	const struct acpi_device_id *ids = data;
+
+	return acpi_match_device(ids, dev) ? 1 : 0;
+}
+
+static int __init dell_lis3lv02d_init(void)
+{
+	struct device *dev;
+	int err;
+
+	/*
+	 * First check for a matching platform_device. This protects against
+	 * SMO88xx ACPI fwnodes which actually do have an I2C resource, which
+	 * will already have an i2c_client instantiated (not a platform_device).
+	 */
+	dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids);
+	if (!dev) {
+		pr_debug("No SMO88xx platform-device found\n");
+		return 0;
+	}
+	put_device(dev);
+
+	lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices);
+	if (!lis3lv02d_dmi_id) {
+		pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
+		return 0;
+	}
+
+	/*
+	 * Register i2c-bus notifier + queue initial scan for lis3lv02d
+	 * i2c_client instantiation.
+	 */
+	err = bus_register_notifier(&i2c_bus_type, &i2c_nb);
+	if (err)
+		return err;
+
+	notifier_registered = true;
+
+	queue_work(system_long_wq, &i2c_work);
+	return 0;
+}
+module_init(dell_lis3lv02d_init);
+
+static void __exit dell_lis3lv02d_module_exit(void)
+{
+	if (!notifier_registered)
+		return;
+
+	bus_unregister_notifier(&i2c_bus_type, &i2c_nb);
+	cancel_work_sync(&i2c_work);
+	i2c_unregister_device(i2c_dev);
+}
+module_exit(dell_lis3lv02d_module_exit);
+
+MODULE_DESCRIPTION("lis3lv02d i2c-client instantiation for ACPI SMO88xx devices");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_LICENSE("GPL");