Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

i2c: smbus: add SMBus Host Notify support

SMBus Host Notify allows a slave device to act as a master on a bus to
notify the host of an interrupt. On Intel chipsets, the functionality
is directly implemented in the firmware. We just need to export a
function to call .alert() on the proper device driver.

i2c_handle_smbus_host_notify() behaves like i2c_handle_smbus_alert().
When called, it schedules a task that will be able to sleep to go through
the list of devices attached to the adapter.

The current implementation allows one Host Notification to be scheduled
while an other is running.

Tested-by: Andrew Duggan <aduggan@synaptics.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>

authored by

Benjamin Tissoires and committed by
Wolfram Sang
e456cd37 b4f21054

+162 -5
+6
Documentation/i2c/smbus-protocol
··· 199 199 200 200 [S] [HostAddr] [Wr] A [DevAddr] A [DataLow] A [DataHigh] A [P] 201 201 202 + This is implemented in the following way in the Linux kernel: 203 + * I2C bus drivers which support SMBus Host Notify should call 204 + i2c_setup_smbus_host_notify() to setup SMBus Host Notify support. 205 + * I2C drivers for devices which can trigger SMBus Host Notify should implement 206 + the optional alert() callback. 207 + 202 208 203 209 Packet Error Checking (PEC) 204 210 ===========================
+108 -5
drivers/i2c/i2c-smbus.c
··· 33 33 34 34 struct alert_data { 35 35 unsigned short addr; 36 - u8 flag:1; 36 + enum i2c_alert_protocol type; 37 + unsigned int data; 37 38 }; 38 39 39 40 /* If this is the alerting device, notify its driver */ ··· 57 56 if (client->dev.driver) { 58 57 driver = to_i2c_driver(client->dev.driver); 59 58 if (driver->alert) 60 - driver->alert(client, I2C_PROTOCOL_SMBUS_ALERT, 61 - data->flag); 59 + driver->alert(client, data->type, data->data); 62 60 else 63 61 dev_warn(&client->dev, "no driver alert()!\n"); 64 62 } else ··· 97 97 if (status < 0) 98 98 break; 99 99 100 - data.flag = status & 1; 100 + data.data = status & 1; 101 101 data.addr = status >> 1; 102 + data.type = I2C_PROTOCOL_SMBUS_ALERT; 102 103 103 104 if (data.addr == prev_addr) { 104 105 dev_warn(&ara->dev, "Duplicate SMBALERT# from dev " ··· 107 106 break; 108 107 } 109 108 dev_dbg(&ara->dev, "SMBALERT# from dev 0x%02x, flag %d\n", 110 - data.addr, data.flag); 109 + data.addr, data.data); 111 110 112 111 /* Notify driver for the device which issued the alert */ 113 112 device_for_each_child(&ara->adapter->dev, &data, ··· 240 239 return schedule_work(&alert->alert); 241 240 } 242 241 EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); 242 + 243 + static void smbus_host_notify_work(struct work_struct *work) 244 + { 245 + struct alert_data alert; 246 + struct i2c_adapter *adapter; 247 + unsigned long flags; 248 + u16 payload; 249 + u8 addr; 250 + struct smbus_host_notify *data; 251 + 252 + data = container_of(work, struct smbus_host_notify, work); 253 + 254 + spin_lock_irqsave(&data->lock, flags); 255 + payload = data->payload; 256 + addr = data->addr; 257 + adapter = data->adapter; 258 + 259 + /* clear the pending bit and release the spinlock */ 260 + data->pending = false; 261 + spin_unlock_irqrestore(&data->lock, flags); 262 + 263 + if (!adapter || !addr) 264 + return; 265 + 266 + alert.type = I2C_PROTOCOL_SMBUS_HOST_NOTIFY; 267 + alert.addr = addr; 268 + alert.data = payload; 269 + 270 + device_for_each_child(&adapter->dev, &alert, smbus_do_alert); 271 + } 272 + 273 + /** 274 + * i2c_setup_smbus_host_notify - Allocate a new smbus_host_notify for the given 275 + * I2C adapter. 276 + * @adapter: the adapter we want to associate a Host Notify function 277 + * 278 + * Returns a struct smbus_host_notify pointer on success, and NULL on failure. 279 + * The resulting smbus_host_notify must not be freed afterwards, it is a 280 + * managed resource already. 281 + */ 282 + struct smbus_host_notify *i2c_setup_smbus_host_notify(struct i2c_adapter *adap) 283 + { 284 + struct smbus_host_notify *host_notify; 285 + 286 + host_notify = devm_kzalloc(&adap->dev, sizeof(struct smbus_host_notify), 287 + GFP_KERNEL); 288 + if (!host_notify) 289 + return NULL; 290 + 291 + host_notify->adapter = adap; 292 + 293 + spin_lock_init(&host_notify->lock); 294 + INIT_WORK(&host_notify->work, smbus_host_notify_work); 295 + 296 + return host_notify; 297 + } 298 + EXPORT_SYMBOL_GPL(i2c_setup_smbus_host_notify); 299 + 300 + /** 301 + * i2c_handle_smbus_host_notify - Forward a Host Notify event to the correct 302 + * I2C client. 303 + * @host_notify: the struct host_notify attached to the relevant adapter 304 + * @data: the Host Notify data which contains the payload and address of the 305 + * client 306 + * Context: can't sleep 307 + * 308 + * Helper function to be called from an I2C bus driver's interrupt 309 + * handler. It will schedule the Host Notify work, in turn calling the 310 + * corresponding I2C device driver's alert function. 311 + * 312 + * host_notify should be a valid pointer previously returned by 313 + * i2c_setup_smbus_host_notify(). 314 + */ 315 + int i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, 316 + unsigned short addr, unsigned int data) 317 + { 318 + unsigned long flags; 319 + struct i2c_adapter *adapter; 320 + 321 + if (!host_notify || !host_notify->adapter) 322 + return -EINVAL; 323 + 324 + adapter = host_notify->adapter; 325 + 326 + spin_lock_irqsave(&host_notify->lock, flags); 327 + 328 + if (host_notify->pending) { 329 + spin_unlock_irqrestore(&host_notify->lock, flags); 330 + dev_warn(&adapter->dev, "Host Notify already scheduled.\n"); 331 + return -EBUSY; 332 + } 333 + 334 + host_notify->payload = data; 335 + host_notify->addr = addr; 336 + 337 + /* Mark that there is a pending notification and release the lock */ 338 + host_notify->pending = true; 339 + spin_unlock_irqrestore(&host_notify->lock, flags); 340 + 341 + return schedule_work(&host_notify->work); 342 + } 343 + EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); 243 344 244 345 module_i2c_driver(smbalert_driver); 245 346
+44
include/linux/i2c-smbus.h
··· 23 23 #define _LINUX_I2C_SMBUS_H 24 24 25 25 #include <linux/i2c.h> 26 + #include <linux/spinlock.h> 27 + #include <linux/workqueue.h> 26 28 27 29 28 30 /** ··· 49 47 struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter, 50 48 struct i2c_smbus_alert_setup *setup); 51 49 int i2c_handle_smbus_alert(struct i2c_client *ara); 50 + 51 + /** 52 + * smbus_host_notify - internal structure used by the Host Notify mechanism. 53 + * @adapter: the I2C adapter associated with this struct 54 + * @work: worker used to schedule the IRQ in the slave device 55 + * @lock: spinlock to check if a notification is already pending 56 + * @pending: flag set when a notification is pending (any new notification will 57 + * be rejected if pending is true) 58 + * @payload: the actual payload of the Host Notify event 59 + * @addr: the address of the slave device which raised the notification 60 + * 61 + * This struct needs to be allocated by i2c_setup_smbus_host_notify() and does 62 + * not need to be freed. Internally, i2c_setup_smbus_host_notify() uses a 63 + * managed resource to clean this up when the adapter get released. 64 + */ 65 + struct smbus_host_notify { 66 + struct i2c_adapter *adapter; 67 + struct work_struct work; 68 + spinlock_t lock; 69 + bool pending; 70 + u16 payload; 71 + u8 addr; 72 + }; 73 + 74 + #if IS_ENABLED(CONFIG_I2C_SMBUS) 75 + struct smbus_host_notify *i2c_setup_smbus_host_notify(struct i2c_adapter *adap); 76 + int i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, 77 + unsigned short addr, unsigned int data); 78 + #else 79 + static inline struct smbus_host_notify * 80 + i2c_setup_smbus_host_notify(struct i2c_adapter *adap) 81 + { 82 + return NULL; 83 + } 84 + 85 + static inline int 86 + i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, 87 + unsigned short addr, unsigned int data) 88 + { 89 + return 0; 90 + } 91 + #endif /* I2C_SMBUS */ 52 92 53 93 #endif /* _LINUX_I2C_SMBUS_H */
+3
include/linux/i2c.h
··· 128 128 129 129 enum i2c_alert_protocol { 130 130 I2C_PROTOCOL_SMBUS_ALERT, 131 + I2C_PROTOCOL_SMBUS_HOST_NOTIFY, 131 132 }; 132 133 133 134 /** ··· 185 184 * The format and meaning of the data value depends on the protocol. 186 185 * For the SMBus alert protocol, there is a single bit of data passed 187 186 * as the alert response's low bit ("event flag"). 187 + * For the SMBus Host Notify protocol, the data corresponds to the 188 + * 16-bit payload data reported by the slave device acting as master. 188 189 */ 189 190 void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol, 190 191 unsigned int data);
+1
include/uapi/linux/i2c.h
··· 102 102 #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 103 103 #define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ 104 104 #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ 105 + #define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000 105 106 106 107 #define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | \ 107 108 I2C_FUNC_SMBUS_WRITE_BYTE)