diff options
-rw-r--r-- | Documentation/i2c/smbus-protocol | 6 | ||||
-rw-r--r-- | drivers/i2c/i2c-smbus.c | 113 | ||||
-rw-r--r-- | include/linux/i2c-smbus.h | 44 | ||||
-rw-r--r-- | include/linux/i2c.h | 3 | ||||
-rw-r--r-- | include/uapi/linux/i2c.h | 1 |
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 | ||
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 | Packet Error Checking (PEC) | 209 | Packet 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 | ||
34 | struct alert_data { | 34 | struct 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 | } |
241 | EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); | 242 | EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); |
242 | 243 | ||
244 | static 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 | */ | ||
283 | struct 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 | } | ||
299 | EXPORT_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 | */ | ||
316 | int 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 | } | ||
344 | EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); | ||
345 | |||
243 | module_i2c_driver(smbalert_driver); | 346 | module_i2c_driver(smbalert_driver); |
244 | 347 | ||
245 | MODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>"); | 348 | MODULE_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); |
49 | int i2c_handle_smbus_alert(struct i2c_client *ara); | 51 | int 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 | */ | ||
67 | struct 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) | ||
77 | struct smbus_host_notify *i2c_setup_smbus_host_notify(struct i2c_adapter *adap); | ||
78 | int i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, | ||
79 | unsigned short addr, unsigned int data); | ||
80 | #else | ||
81 | static inline struct smbus_host_notify * | ||
82 | i2c_setup_smbus_host_notify(struct i2c_adapter *adap) | ||
83 | { | ||
84 | return NULL; | ||
85 | } | ||
86 | |||
87 | static inline int | ||
88 | i2c_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 | ||
129 | enum i2c_alert_protocol { | 129 | enum 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) |