Quelle i2c-smbus.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* i2c - smbus . c - SMBus extensions to the I2C protocol
*
* Copyright ( C ) 2008 David Brownell
* Copyright ( C ) 2010 - 2019 Jean Delvare < jdelvare @ suse . de >
*/
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/i2c-smbus.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
struct i2c_smbus_alert {
struct work_struct alert;
struct i2c_client *ara; /* Alert response address */
};
struct alert_data {
unsigned short addr;
enum i2c_alert_protocol type;
unsigned int data;
};
/* If this is the alerting device, notify its driver */
static int smbus_do_alert(struct device *dev, void *addrp)
{
struct i2c_client *client = i2c_verify_client(dev);
struct alert_data *data = addrp;
struct i2c_driver *driver;
int ret;
if (!client || client->addr != data->addr)
return 0 ;
if (client->flags & I2C_CLIENT_TEN)
return 0 ;
/*
* Drivers should either disable alerts , or provide at least
* a minimal handler . Lock so the driver won ' t change .
*/
device_lock(dev);
if (client->dev.driver) {
driver = to_i2c_driver(client->dev.driver);
if (driver->alert) {
/* Stop iterating after we find the device */
driver->alert(client, data->type, data->data);
ret = -EBUSY;
} else {
dev_warn(&client->dev, "no driver alert()!\n" );
ret = -EOPNOTSUPP;
}
} else {
dev_dbg(&client->dev, "alert with no driver\n" );
ret = -ENODEV;
}
device_unlock(dev);
return ret;
}
/* Same as above, but call back all drivers with alert handler */
static int smbus_do_alert_force(struct device *dev, void *addrp)
{
struct i2c_client *client = i2c_verify_client(dev);
struct alert_data *data = addrp;
struct i2c_driver *driver;
if (!client || (client->flags & I2C_CLIENT_TEN))
return 0 ;
/*
* Drivers should either disable alerts , or provide at least
* a minimal handler . Lock so the driver won ' t change .
*/
device_lock(dev);
if (client->dev.driver) {
driver = to_i2c_driver(client->dev.driver);
if (driver->alert)
driver->alert(client, data->type, data->data);
}
device_unlock(dev);
return 0 ;
}
/*
* The alert IRQ handler needs to hand work off to a task which can issue
* SMBus calls , because those sleeping calls can ' t be made in IRQ context .
*/
static irqreturn_t smbus_alert(int irq, void *d)
{
struct i2c_smbus_alert *alert = d;
struct i2c_client *ara;
unsigned short prev_addr = I2C_CLIENT_END; /* Not a valid address */
ara = alert->ara;
for (;;) {
s32 status;
struct alert_data data;
/*
* Devices with pending alerts reply in address order , low
* to high , because of slave transmit arbitration . After
* responding , an SMBus device stops asserting SMBALERT # .
*
* Note that SMBus 2 . 0 reserves 10 - bit addresses for future
* use . We neither handle them , nor try to use PEC here .
*/
status = i2c_smbus_read_byte(ara);
if (status < 0 )
break ;
data.data = status & 1 ;
data.addr = status >> 1 ;
data.type = I2C_PROTOCOL_SMBUS_ALERT;
dev_dbg(&ara->dev, "SMBALERT# from dev 0x%02x, flag %d\n" ,
data.addr, data.data);
/* Notify driver for the device which issued the alert */
status = device_for_each_child(&ara->adapter->dev, &data,
smbus_do_alert);
/*
* If we read the same address more than once , and the alert
* was not handled by a driver , it won ' t do any good to repeat
* the loop because it will never terminate . Try again , this
* time calling the alert handlers of all devices connected to
* the bus , and abort the loop afterwards . If this helps , we
* are all set . If it doesn ' t , there is nothing else we can do ,
* so we might as well abort the loop .
* Note : This assumes that a driver with alert handler handles
* the alert properly and clears it if necessary .
*/
if (data.addr == prev_addr && status != -EBUSY) {
device_for_each_child(&ara->adapter->dev, &data,
smbus_do_alert_force);
break ;
}
prev_addr = data.addr;
}
return IRQ_HANDLED;
}
static void smbalert_work(struct work_struct *work)
{
struct i2c_smbus_alert *alert;
alert = container_of(work, struct i2c_smbus_alert, alert);
smbus_alert(0 , alert);
}
/* Setup SMBALERT# infrastructure */
static int smbalert_probe(struct i2c_client *ara)
{
struct i2c_smbus_alert_setup *setup = dev_get_platdata(&ara->dev);
struct i2c_smbus_alert *alert;
struct i2c_adapter *adapter = ara->adapter;
unsigned long irqflags = IRQF_SHARED | IRQF_ONESHOT;
struct gpio_desc *gpiod;
int res, irq;
alert = devm_kzalloc(&ara->dev, sizeof (struct i2c_smbus_alert),
GFP_KERNEL);
if (!alert)
return -ENOMEM;
if (setup) {
irq = setup->irq;
} else {
irq = fwnode_irq_get_byname(dev_fwnode(adapter->dev.parent),
"smbus_alert" );
if (irq <= 0 ) {
gpiod = devm_gpiod_get(adapter->dev.parent, "smbalert" , GPIOD_IN);
if (IS_ERR(gpiod))
return PTR_ERR(gpiod);
irq = gpiod_to_irq(gpiod);
if (irq <= 0 )
return irq;
irqflags |= IRQF_TRIGGER_FALLING;
}
}
INIT_WORK(&alert->alert, smbalert_work);
alert->ara = ara;
if (irq > 0 ) {
res = devm_request_threaded_irq(&ara->dev, irq, NULL, smbus_alert,
irqflags, "smbus_alert" , alert);
if (res)
return res;
}
i2c_set_clientdata(ara, alert);
dev_info(&adapter->dev, "supports SMBALERT#\n" );
return 0 ;
}
/* IRQ and memory resources are managed so they are freed automatically */
static void smbalert_remove(struct i2c_client *ara)
{
struct i2c_smbus_alert *alert = i2c_get_clientdata(ara);
cancel_work_sync(&alert->alert);
}
static const struct i2c_device_id smbalert_ids[] = {
{ "smbus_alert" },
{ /* LIST END */ }
};
MODULE_DEVICE_TABLE(i2c, smbalert_ids);
static struct i2c_driver smbalert_driver = {
.driver = {
.name = "smbus_alert" ,
},
.probe = smbalert_probe,
.remove = smbalert_remove,
.id_table = smbalert_ids,
};
/**
* i2c_handle_smbus_alert - Handle an SMBus alert
* @ ara : the ARA client on the relevant adapter
* Context : can ' t sleep
*
* Helper function to be called from an I2C bus driver ' s interrupt
* handler . It will schedule the alert work , in turn calling the
* corresponding I2C device driver ' s alert function .
*
* It is assumed that ara is a valid i2c client previously returned by
* i2c_new_smbus_alert_device ( ) .
*/
int i2c_handle_smbus_alert(struct i2c_client *ara)
{
struct i2c_smbus_alert *alert = i2c_get_clientdata(ara);
return schedule_work(&alert->alert);
}
EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert);
module_i2c_driver(smbalert_driver);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
#define SMBUS_HOST_NOTIFY_LEN 3
struct i2c_slave_host_notify_status {
u8 index;
u8 addr;
};
static int i2c_slave_host_notify_cb(struct i2c_client *client,
enum i2c_slave_event event, u8 *val)
{
struct i2c_slave_host_notify_status *status = client->dev.platform_data;
switch (event) {
case I2C_SLAVE_WRITE_RECEIVED:
/* We only retrieve the first byte received (addr)
* since there is currently no support to retrieve the data
* parameter from the client .
*/
if (status->index == 0 )
status->addr = *val;
if (status->index < U8_MAX)
status->index++;
break ;
case I2C_SLAVE_STOP:
if (status->index == SMBUS_HOST_NOTIFY_LEN)
i2c_handle_smbus_host_notify(client->adapter,
status->addr);
fallthrough;
case I2C_SLAVE_WRITE_REQUESTED:
status->index = 0 ;
break ;
case I2C_SLAVE_READ_REQUESTED:
case I2C_SLAVE_READ_PROCESSED:
*val = 0 xff;
break ;
}
return 0 ;
}
/**
* i2c_new_slave_host_notify_device - get a client for SMBus host - notify support
* @ adapter : the target adapter
* Context : can sleep
*
* Setup handling of the SMBus host - notify protocol on a given I2C bus segment .
*
* Handling is done by creating a device and its callback and handling data
* received via the SMBus host - notify address ( 0 x8 )
*
* This returns the client , which should be ultimately freed using
* i2c_free_slave_host_notify_device ( ) ; or an ERRPTR to indicate an error .
*/
struct i2c_client *i2c_new_slave_host_notify_device(struct i2c_adapter *adapter)
{
struct i2c_board_info host_notify_board_info = {
I2C_BOARD_INFO("smbus_host_notify" , 0 x08),
.flags = I2C_CLIENT_SLAVE,
};
struct i2c_slave_host_notify_status *status;
struct i2c_client *client;
int ret;
status = kzalloc(sizeof (struct i2c_slave_host_notify_status),
GFP_KERNEL);
if (!status)
return ERR_PTR(-ENOMEM);
host_notify_board_info.platform_data = status;
client = i2c_new_client_device(adapter, &host_notify_board_info);
if (IS_ERR(client)) {
kfree(status);
return client;
}
ret = i2c_slave_register(client, i2c_slave_host_notify_cb);
if (ret) {
i2c_unregister_device(client);
kfree(status);
return ERR_PTR(ret);
}
return client;
}
EXPORT_SYMBOL_GPL(i2c_new_slave_host_notify_device);
/**
* i2c_free_slave_host_notify_device - free the client for SMBus host - notify
* support
* @ client : the client to free
* Context : can sleep
*
* Free the i2c_client allocated via i2c_new_slave_host_notify_device
*/
void i2c_free_slave_host_notify_device(struct i2c_client *client)
{
if (IS_ERR_OR_NULL(client))
return ;
i2c_slave_unregister(client);
kfree(client->dev.platform_data);
i2c_unregister_device(client);
}
EXPORT_SYMBOL_GPL(i2c_free_slave_host_notify_device);
#endif
/*
* SPD is not part of SMBus but we include it here for convenience as the
* target systems are the same .
* Restrictions to automatic SPD instantiation :
* - Only works if all filled slots have the same memory type
* - Only works for ( LP ) DDR memory types up to DDR5
* - Only works on systems with 1 to 8 memory slots
*/
#if IS_ENABLED(CONFIG_DMI)
static void i2c_register_spd(struct i2c_adapter *adap, bool write_disabled)
{
int n, slot_count = 0 , dimm_count = 0 ;
u16 handle;
u8 common_mem_type = 0 x0, mem_type;
u64 mem_size;
bool instantiate = true ;
const char *name;
while ((handle = dmi_memdev_handle(slot_count)) != 0 xffff) {
slot_count++;
/* Skip empty slots */
mem_size = dmi_memdev_size(handle);
if (!mem_size)
continue ;
/* Skip undefined memory type */
mem_type = dmi_memdev_type(handle);
if (mem_type <= 0 x02) /* Invalid, Other, Unknown */
continue ;
if (!common_mem_type) {
/* First filled slot */
common_mem_type = mem_type;
} else {
/* Check that all filled slots have the same type */
if (mem_type != common_mem_type) {
dev_warn(&adap->dev,
"Different memory types mixed, not instantiating SPD\n" );
return ;
}
}
dimm_count++;
}
/* No useful DMI data, bail out */
if (!dimm_count)
return ;
/*
* The max number of SPD EEPROMs that can be addressed per bus is 8 .
* If more slots are present either muxed or multiple busses are
* necessary or the additional slots are ignored .
*/
slot_count = min(slot_count, 8 );
/*
* Memory types could be found at section 7 . 18 . 2 ( Memory Device — Type ) , table 78
* https : //www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.6.0.pdf
*/
switch (common_mem_type) {
case 0 x12: /* DDR */
case 0 x13: /* DDR2 */
case 0 x18: /* DDR3 */
case 0 x1B: /* LPDDR */
case 0 x1C: /* LPDDR2 */
case 0 x1D: /* LPDDR3 */
name = "spd" ;
break ;
case 0 x1A: /* DDR4 */
case 0 x1E: /* LPDDR4 */
name = "ee1004" ;
break ;
case 0 x22: /* DDR5 */
case 0 x23: /* LPDDR5 */
name = "spd5118" ;
instantiate = !write_disabled;
break ;
default :
dev_info(&adap->dev,
"Memory type 0x%02x not supported yet, not instantiating SPD\n" ,
common_mem_type);
return ;
}
/*
* We don ' t know in which slots the memory modules are . We could
* try to guess from the slot names , but that would be rather complex
* and unreliable , so better probe all possible addresses until we
* have found all memory modules .
*/
for (n = 0 ; n < slot_count && dimm_count; n++) {
struct i2c_board_info info;
unsigned short addr_list[2 ];
memset(&info, 0 , sizeof (struct i2c_board_info));
strscpy(info.type, name, I2C_NAME_SIZE);
addr_list[0 ] = 0 x50 + n;
addr_list[1 ] = I2C_CLIENT_END;
if (!instantiate)
continue ;
if (!IS_ERR(i2c_new_scanned_device(adap, &info, addr_list, NULL))) {
dev_info(&adap->dev,
"Successfully instantiated SPD at 0x%hx\n" ,
addr_list[0 ]);
dimm_count--;
}
}
}
void i2c_register_spd_write_disable(struct i2c_adapter *adap)
{
i2c_register_spd(adap, true );
}
EXPORT_SYMBOL_GPL(i2c_register_spd_write_disable);
void i2c_register_spd_write_enable(struct i2c_adapter *adap)
{
i2c_register_spd(adap, false );
}
EXPORT_SYMBOL_GPL(i2c_register_spd_write_enable);
#endif
MODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>" );
MODULE_DESCRIPTION("SMBus protocol extensions support" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=92 H=97 G=94
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-09-30)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691