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