Skip to content
Snippets Groups Projects
Commit 789aa3e7 authored by Augusto Caringi's avatar Augusto Caringi
Browse files
parents f4ca2d23 145e9bd8
No related branches found
No related tags found
No related merge requests found
......@@ -49,6 +49,7 @@ Supported adapters:
* Intel Meteor Lake (SOC and PCH)
* Intel Birch Stream (SOC)
* Intel Arrow Lake (SOC)
* Intel Panther Lake (SOC)
Datasheets: Publicly available at the Intel website
......
......@@ -160,10 +160,19 @@ config I2C_I801
Meteor Lake (SOC and PCH)
Birch Stream (SOC)
Arrow Lake (SOC)
Panther Lake (SOC)
This driver can also be built as a module. If so, the module
will be called i2c-i801.
config I2C_I801_MUX
def_bool I2C_I801
depends on DMI && I2C_MUX_GPIO
depends on !(I2C_I801=y && I2C_MUX=m)
help
Optional support for multiplexed SMBUS on certain systems with
more than 8 memory slots.
config I2C_ISCH
tristate "Intel SCH SMBus 1.0"
depends on PCI
......
This diff is collapsed.
......@@ -140,7 +140,8 @@ config DELL_SMBIOS_SMM
config DELL_SMO8800
tristate "Dell Latitude freefall driver (ACPI SMO88XX)"
default m
depends on ACPI
depends on I2C
depends on ACPI || COMPILE_TEST
help
Say Y here if you want to support SMO88XX freefall devices
on Dell Latitude laptops.
......
......@@ -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_WMI) += dell-wmi.o
dell-wmi-objs := dell-wmi-base.o
dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o
......
// 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 LIS3_WHO_AM_I 0x0f
#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[] __initconst = {
/*
* 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("Latitude E6330", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6430", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Precision 3540", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Precision M6800", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Vostro V131", 0x1d),
DELL_LIS3LV02D_DMI_ENTRY("Vostro 5568", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("XPS 15 7590", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("XPS 15 9550", 0x29),
{ }
};
static u8 i2c_addr;
static struct i2c_client *i2c_dev;
static bool notifier_registered;
static bool probe_i2c_addr;
module_param(probe_i2c_addr, bool, 0444);
MODULE_PARM_DESC(probe_i2c_addr, "Probe the i801 I2C bus for the accelerometer on models where the address is unknown, this may be dangerous.");
static int detect_lis3lv02d(struct i2c_adapter *adap, unsigned short addr)
{
union i2c_smbus_data smbus_data;
int err;
dev_info(&adap->dev, "Probing for lis3lv02d on address 0x%02x\n", addr);
err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, LIS3_WHO_AM_I,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (err < 0)
return 0; /* Not found */
/* valid who-am-i values are from drivers/misc/lis3lv02d/lis3lv02d.c */
switch (smbus_data.byte) {
case 0x32:
case 0x33:
case 0x3a:
case 0x3b:
break;
default:
dev_warn(&adap->dev, "Unknown who-am-i register value 0x%02x\n",
smbus_data.byte);
return 0; /* Not found */
}
dev_info(&adap->dev,
"Detected lis3lv02d on address 0x%02x, please report this upstream to platform-driver-x86@vger.kernel.org so that a quirk can be added\n",
addr);
return 1; /* Found */
}
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() and not i2c_for_each_dev() to avoid
* a deadlock when find_i801() calls i2c_get_adapter().
*/
bus_for_each_dev(&i2c_bus_type, NULL, &adap, find_i801);
if (!adap)
return;
strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
if (i2c_addr) {
info.addr = i2c_addr;
i2c_dev = i2c_new_client_device(adap, &info);
} else {
/* First try address 0x29 (most used) and then try 0x1d */
static const unsigned short addr_list[] = { 0x29, 0x1d, I2C_CLIENT_END };
i2c_dev = i2c_new_scanned_device(adap, &info, addr_list, detect_lis3lv02d);
}
if (IS_ERR(i2c_dev)) {
dev_err(&adap->dev, "error %ld registering i2c_client\n", PTR_ERR(i2c_dev));
i2c_dev = NULL;
} else {
dev_dbg(&adap->dev, "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) {
dev_dbg(&client->adapter->dev, "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 __init match_acpi_device_ids(struct device *dev, const void *data)
{
return acpi_match_device(data, dev) ? 1 : 0;
}
static int __init dell_lis3lv02d_init(void)
{
const struct dmi_system_id *lis3lv02d_dmi_id;
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 && !probe_i2c_addr) {
pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
pr_info("Pass dell_lis3lv02d.probe_i2c_addr=1 on the kernel command line to probe, this may be dangerous!\n");
return 0;
}
if (lis3lv02d_dmi_id)
i2c_addr = (long)lis3lv02d_dmi_id->driver_data;
/*
* 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");
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ACPI SMO88XX lis3lv02d freefall / accelerometer device-ids.
*
* Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com>
* Copyright (C) 2014 Pali Rohár <pali@kernel.org>
*/
#ifndef _DELL_SMO8800_IDS_H_
#define _DELL_SMO8800_IDS_H_
#include <linux/mod_devicetable.h>
#include <linux/module.h>
static const struct acpi_device_id smo8800_ids[] = {
{ "SMO8800" },
{ "SMO8801" },
{ "SMO8810" },
{ "SMO8811" },
{ "SMO8820" },
{ "SMO8821" },
{ "SMO8830" },
{ "SMO8831" },
{ }
};
MODULE_DEVICE_TABLE(acpi, smo8800_ids);
#endif
......@@ -10,13 +10,14 @@
#define DRIVER_NAME "smo8800"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include "dell-smo8800-ids.h"
struct smo8800_device {
u32 irq; /* acpi device irq */
......@@ -44,37 +45,6 @@ static irqreturn_t smo8800_interrupt_thread(int irq, void *data)
return IRQ_HANDLED;
}
static acpi_status smo8800_get_resource(struct acpi_resource *resource,
void *context)
{
struct acpi_resource_extended_irq *irq;
if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ)
return AE_OK;
irq = &resource->data.extended_irq;
if (!irq || !irq->interrupt_count)
return AE_OK;
*((u32 *)context) = irq->interrupts[0];
return AE_CTRL_TERMINATE;
}
static u32 smo8800_get_irq(struct acpi_device *device)
{
u32 irq = 0;
acpi_status status;
status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
smo8800_get_resource, &irq);
if (ACPI_FAILURE(status)) {
dev_err(&device->dev, "acpi_walk_resources failed\n");
return 0;
}
return irq;
}
static ssize_t smo8800_misc_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
......@@ -97,10 +67,7 @@ static ssize_t smo8800_misc_read(struct file *file, char __user *buf,
retval = 1;
if (data < 255)
byte_data = data;
else
byte_data = 255;
byte_data = min_t(u32, data, 255);
if (put_user(byte_data, buf))
retval = -EFAULT;
......@@ -136,7 +103,7 @@ static const struct file_operations smo8800_misc_fops = {
.release = smo8800_misc_release,
};
static int smo8800_add(struct acpi_device *device)
static int smo8800_probe(struct platform_device *device)
{
int err;
struct smo8800_device *smo8800;
......@@ -160,14 +127,12 @@ static int smo8800_add(struct acpi_device *device)
return err;
}
device->driver_data = smo8800;
platform_set_drvdata(device, smo8800);
smo8800->irq = smo8800_get_irq(device);
if (!smo8800->irq) {
dev_err(&device->dev, "failed to obtain IRQ\n");
err = -EINVAL;
err = platform_get_irq(device, 0);
if (err < 0)
goto error;
}
smo8800->irq = err;
err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick,
smo8800_interrupt_thread,
......@@ -189,42 +154,24 @@ static int smo8800_add(struct acpi_device *device)
return err;
}
static void smo8800_remove(struct acpi_device *device)
static void smo8800_remove(struct platform_device *device)
{
struct smo8800_device *smo8800 = device->driver_data;
struct smo8800_device *smo8800 = platform_get_drvdata(device);
free_irq(smo8800->irq, smo8800);
misc_deregister(&smo8800->miscdev);
dev_dbg(&device->dev, "device /dev/freefall unregistered\n");
}
/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */
static const struct acpi_device_id smo8800_ids[] = {
{ "SMO8800", 0 },
{ "SMO8801", 0 },
{ "SMO8810", 0 },
{ "SMO8811", 0 },
{ "SMO8820", 0 },
{ "SMO8821", 0 },
{ "SMO8830", 0 },
{ "SMO8831", 0 },
{ "", 0 },
};
MODULE_DEVICE_TABLE(acpi, smo8800_ids);
static struct acpi_driver smo8800_driver = {
.name = DRIVER_NAME,
.class = "Latitude",
.ids = smo8800_ids,
.ops = {
.add = smo8800_add,
.remove = smo8800_remove,
static struct platform_driver smo8800_driver = {
.probe = smo8800_probe,
.remove_new = smo8800_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = smo8800_ids,
},
.owner = THIS_MODULE,
};
module_acpi_driver(smo8800_driver);
module_platform_driver(smo8800_driver);
MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)");
MODULE_LICENSE("GPL");
......
......@@ -1549,4 +1549,9 @@ static inline bool acpi_node_backed_by_real_pxm(int nid)
}
#endif
static inline void acpi_use_parent_companion(struct device *dev)
{
ACPI_COMPANION_SET(dev, ACPI_COMPANION(dev->parent));
}
#endif /*_LINUX_ACPI_H*/
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment