]>
Commit | Line | Data |
---|---|---|
fc0bdd99 IY |
1 | /* |
2 | * PC SMBus implementation | |
8fa21b80 | 3 | * split from acpi.c |
fc0bdd99 IY |
4 | * |
5 | * Copyright (c) 2006 Fabrice Bellard | |
6 | * | |
7 | * This library is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
61f3c91a | 9 | * License version 2.1 as published by the Free Software Foundation. |
fc0bdd99 IY |
10 | * |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
1012e960 BS |
17 | * License along with this library; if not, see |
18 | * <http://www.gnu.org/licenses/>. | |
fc0bdd99 | 19 | */ |
d6454270 | 20 | |
b6a0aa05 | 21 | #include "qemu/osdep.h" |
4ab2f2a8 | 22 | #include "hw/boards.h" |
0d09e41a | 23 | #include "hw/i2c/pm_smbus.h" |
93198b6c | 24 | #include "hw/i2c/smbus_master.h" |
d6454270 | 25 | #include "migration/vmstate.h" |
c6e1b31b | 26 | #include "trace.h" |
fc0bdd99 | 27 | |
fc0bdd99 IY |
28 | #define SMBHSTSTS 0x00 |
29 | #define SMBHSTCNT 0x02 | |
30 | #define SMBHSTCMD 0x03 | |
31 | #define SMBHSTADD 0x04 | |
32 | #define SMBHSTDAT0 0x05 | |
33 | #define SMBHSTDAT1 0x06 | |
34 | #define SMBBLKDAT 0x07 | |
38ad4fae | 35 | #define SMBAUXCTL 0x0d |
fc0bdd99 | 36 | |
b8fb9043 CM |
37 | #define STS_HOST_BUSY (1 << 0) |
38 | #define STS_INTR (1 << 1) | |
39 | #define STS_DEV_ERR (1 << 2) | |
40 | #define STS_BUS_ERR (1 << 3) | |
41 | #define STS_FAILED (1 << 4) | |
42 | #define STS_SMBALERT (1 << 5) | |
43 | #define STS_INUSE_STS (1 << 6) | |
44 | #define STS_BYTE_DONE (1 << 7) | |
edb5092c M |
45 | /* Signs of successfully transaction end : |
46 | * ByteDoneStatus = 1 (STS_BYTE_DONE) and INTR = 1 (STS_INTR ) | |
47 | */ | |
48 | ||
b8fb9043 CM |
49 | #define CTL_INTREN (1 << 0) |
50 | #define CTL_KILL (1 << 1) | |
51 | #define CTL_LAST_BYTE (1 << 5) | |
52 | #define CTL_START (1 << 6) | |
53 | #define CTL_PEC_EN (1 << 7) | |
54 | #define CTL_RETURN_MASK 0x1f | |
55 | ||
56 | #define PROT_QUICK 0 | |
57 | #define PROT_BYTE 1 | |
58 | #define PROT_BYTE_DATA 2 | |
59 | #define PROT_WORD_DATA 3 | |
60 | #define PROT_PROC_CALL 4 | |
61 | #define PROT_BLOCK_DATA 5 | |
00bdfeab | 62 | #define PROT_I2C_BLOCK_READ 6 |
b8fb9043 | 63 | |
38ad4fae CM |
64 | #define AUX_PEC (1 << 0) |
65 | #define AUX_BLK (1 << 1) | |
66 | #define AUX_MASK 0x3 | |
67 | ||
fc0bdd99 IY |
68 | static void smb_transaction(PMSMBus *s) |
69 | { | |
70 | uint8_t prot = (s->smb_ctl >> 2) & 0x07; | |
71 | uint8_t read = s->smb_addr & 0x01; | |
72 | uint8_t cmd = s->smb_cmd; | |
73 | uint8_t addr = s->smb_addr >> 1; | |
a5c82852 | 74 | I2CBus *bus = s->smbus; |
c8097612 | 75 | int ret; |
fc0bdd99 | 76 | |
c6e1b31b | 77 | trace_smbus_transaction(addr, prot); |
edb5092c M |
78 | /* Transaction isn't exec if STS_DEV_ERR bit set */ |
79 | if ((s->smb_stat & STS_DEV_ERR) != 0) { | |
c8097612 PB |
80 | goto error; |
81 | } | |
b8fb9043 | 82 | |
fc0bdd99 | 83 | switch(prot) { |
b8fb9043 | 84 | case PROT_QUICK: |
c8097612 PB |
85 | ret = smbus_quick_command(bus, addr, read); |
86 | goto done; | |
b8fb9043 | 87 | case PROT_BYTE: |
fc0bdd99 | 88 | if (read) { |
c8097612 PB |
89 | ret = smbus_receive_byte(bus, addr); |
90 | goto data8; | |
fc0bdd99 | 91 | } else { |
c8097612 PB |
92 | ret = smbus_send_byte(bus, addr, cmd); |
93 | goto done; | |
fc0bdd99 | 94 | } |
b8fb9043 | 95 | case PROT_BYTE_DATA: |
fc0bdd99 | 96 | if (read) { |
c8097612 PB |
97 | ret = smbus_read_byte(bus, addr, cmd); |
98 | goto data8; | |
fc0bdd99 | 99 | } else { |
c8097612 PB |
100 | ret = smbus_write_byte(bus, addr, cmd, s->smb_data0); |
101 | goto done; | |
fc0bdd99 IY |
102 | } |
103 | break; | |
b8fb9043 | 104 | case PROT_WORD_DATA: |
fc0bdd99 | 105 | if (read) { |
c8097612 PB |
106 | ret = smbus_read_word(bus, addr, cmd); |
107 | goto data16; | |
fc0bdd99 | 108 | } else { |
b8fb9043 CM |
109 | ret = smbus_write_word(bus, addr, cmd, |
110 | (s->smb_data1 << 8) | s->smb_data0); | |
c8097612 | 111 | goto done; |
fc0bdd99 IY |
112 | } |
113 | break; | |
00bdfeab | 114 | case PROT_I2C_BLOCK_READ: |
52cc6a49 CM |
115 | /* According to the Linux i2c-i801 driver: |
116 | * NB: page 240 of ICH5 datasheet shows that the R/#W | |
117 | * bit should be cleared here, even when reading. | |
118 | * However if SPD Write Disable is set (Lynx Point and later), | |
119 | * the read will fail if we don't set the R/#W bit. | |
120 | * So at least Linux may or may not set the read bit here. | |
121 | * So just ignore the read bit for this command. | |
122 | */ | |
90603c5b | 123 | if (i2c_start_send(bus, addr)) { |
00bdfeab | 124 | goto error; |
fc0bdd99 | 125 | } |
52cc6a49 CM |
126 | ret = i2c_send(bus, s->smb_data1); |
127 | if (ret) { | |
128 | goto error; | |
129 | } | |
90603c5b | 130 | if (i2c_start_recv(bus, addr)) { |
52cc6a49 CM |
131 | goto error; |
132 | } | |
133 | s->in_i2c_block_read = true; | |
134 | s->smb_blkdata = i2c_recv(s->smbus); | |
135 | s->op_done = false; | |
136 | s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE; | |
137 | goto out; | |
138 | ||
38ad4fae CM |
139 | case PROT_BLOCK_DATA: |
140 | if (read) { | |
141 | ret = smbus_read_block(bus, addr, cmd, s->smb_data, | |
142 | sizeof(s->smb_data), !s->i2c_enable, | |
143 | !s->i2c_enable); | |
144 | if (ret < 0) { | |
145 | goto error; | |
146 | } | |
147 | s->smb_index = 0; | |
148 | s->op_done = false; | |
149 | if (s->smb_auxctl & AUX_BLK) { | |
150 | s->smb_stat |= STS_INTR; | |
151 | } else { | |
152 | s->smb_blkdata = s->smb_data[0]; | |
153 | s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE; | |
154 | } | |
155 | s->smb_data0 = ret; | |
156 | goto out; | |
157 | } else { | |
158 | if (s->smb_auxctl & AUX_BLK) { | |
159 | if (s->smb_index != s->smb_data0) { | |
160 | s->smb_index = 0; | |
161 | goto error; | |
162 | } | |
163 | /* Data is already all written to the queue, just do | |
164 | the operation. */ | |
165 | s->smb_index = 0; | |
166 | ret = smbus_write_block(bus, addr, cmd, s->smb_data, | |
167 | s->smb_data0, !s->i2c_enable); | |
168 | if (ret < 0) { | |
169 | goto error; | |
170 | } | |
171 | s->op_done = true; | |
172 | s->smb_stat |= STS_INTR; | |
173 | s->smb_stat &= ~STS_HOST_BUSY; | |
174 | } else { | |
175 | s->op_done = false; | |
176 | s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE; | |
177 | s->smb_data[0] = s->smb_blkdata; | |
178 | s->smb_index = 0; | |
38ad4fae CM |
179 | } |
180 | goto out; | |
181 | } | |
182 | break; | |
fc0bdd99 IY |
183 | default: |
184 | goto error; | |
185 | } | |
c8097612 PB |
186 | abort(); |
187 | ||
188 | data16: | |
189 | if (ret < 0) { | |
190 | goto error; | |
191 | } | |
192 | s->smb_data1 = ret >> 8; | |
193 | data8: | |
194 | if (ret < 0) { | |
195 | goto error; | |
196 | } | |
197 | s->smb_data0 = ret; | |
198 | done: | |
199 | if (ret < 0) { | |
200 | goto error; | |
201 | } | |
38ad4fae CM |
202 | s->smb_stat |= STS_INTR; |
203 | out: | |
fc0bdd99 IY |
204 | return; |
205 | ||
c8097612 | 206 | error: |
edb5092c | 207 | s->smb_stat |= STS_DEV_ERR; |
c8097612 | 208 | return; |
fc0bdd99 IY |
209 | } |
210 | ||
880b1ffe HP |
211 | static void smb_transaction_start(PMSMBus *s) |
212 | { | |
12bd93c1 CM |
213 | if (s->smb_ctl & CTL_INTREN) { |
214 | smb_transaction(s); | |
52cc6a49 | 215 | s->start_transaction_on_status_read = false; |
12bd93c1 CM |
216 | } else { |
217 | /* Do not execute immediately the command; it will be | |
218 | * executed when guest will read SMB_STAT register. This | |
219 | * is to work around a bug in AMIBIOS (that is working | |
220 | * around another bug in some specific hardware) where | |
221 | * it waits for STS_HOST_BUSY to be set before waiting | |
222 | * checking for status. If STS_HOST_BUSY doesn't get | |
223 | * set, it gets stuck. */ | |
224 | s->smb_stat |= STS_HOST_BUSY; | |
52cc6a49 | 225 | s->start_transaction_on_status_read = true; |
12bd93c1 | 226 | } |
880b1ffe HP |
227 | } |
228 | ||
e724385a CM |
229 | static bool |
230 | smb_irq_value(PMSMBus *s) | |
231 | { | |
232 | return ((s->smb_stat & ~STS_HOST_BUSY) != 0) && (s->smb_ctl & CTL_INTREN); | |
233 | } | |
234 | ||
52cc6a49 CM |
235 | static bool |
236 | smb_byte_by_byte(PMSMBus *s) | |
237 | { | |
238 | if (s->op_done) { | |
239 | return false; | |
240 | } | |
241 | if (s->in_i2c_block_read) { | |
242 | return true; | |
243 | } | |
244 | return !(s->smb_auxctl & AUX_BLK); | |
245 | } | |
246 | ||
798512e5 GH |
247 | static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val, |
248 | unsigned width) | |
fc0bdd99 IY |
249 | { |
250 | PMSMBus *s = opaque; | |
52cc6a49 | 251 | uint8_t clear_byte_done; |
798512e5 | 252 | |
c6e1b31b | 253 | trace_smbus_ioport_writeb(addr, val); |
fc0bdd99 IY |
254 | switch(addr) { |
255 | case SMBHSTSTS: | |
52cc6a49 | 256 | clear_byte_done = s->smb_stat & val & STS_BYTE_DONE; |
38ad4fae | 257 | s->smb_stat &= ~(val & ~STS_HOST_BUSY); |
52cc6a49 | 258 | if (clear_byte_done && smb_byte_by_byte(s)) { |
38ad4fae CM |
259 | uint8_t read = s->smb_addr & 0x01; |
260 | ||
52cc6a49 CM |
261 | if (s->in_i2c_block_read) { |
262 | /* See comment below PROT_I2C_BLOCK_READ above. */ | |
263 | read = 1; | |
264 | } | |
265 | ||
38ad4fae | 266 | s->smb_index++; |
f2609ffd PP |
267 | if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) { |
268 | s->smb_index = 0; | |
269 | } | |
38ad4fae CM |
270 | if (!read && s->smb_index == s->smb_data0) { |
271 | uint8_t prot = (s->smb_ctl >> 2) & 0x07; | |
272 | uint8_t cmd = s->smb_cmd; | |
973d3ea5 | 273 | uint8_t smb_addr = s->smb_addr >> 1; |
38ad4fae CM |
274 | int ret; |
275 | ||
276 | if (prot == PROT_I2C_BLOCK_READ) { | |
277 | s->smb_stat |= STS_DEV_ERR; | |
278 | goto out; | |
279 | } | |
280 | ||
973d3ea5 | 281 | ret = smbus_write_block(s->smbus, smb_addr, cmd, s->smb_data, |
38ad4fae CM |
282 | s->smb_data0, !s->i2c_enable); |
283 | if (ret < 0) { | |
284 | s->smb_stat |= STS_DEV_ERR; | |
285 | goto out; | |
286 | } | |
287 | s->op_done = true; | |
288 | s->smb_stat |= STS_INTR; | |
289 | s->smb_stat &= ~STS_HOST_BUSY; | |
290 | } else if (!read) { | |
291 | s->smb_data[s->smb_index] = s->smb_blkdata; | |
292 | s->smb_stat |= STS_BYTE_DONE; | |
293 | } else if (s->smb_ctl & CTL_LAST_BYTE) { | |
294 | s->op_done = true; | |
52cc6a49 CM |
295 | if (s->in_i2c_block_read) { |
296 | s->in_i2c_block_read = false; | |
297 | s->smb_blkdata = i2c_recv(s->smbus); | |
298 | i2c_nack(s->smbus); | |
299 | i2c_end_transfer(s->smbus); | |
300 | } else { | |
301 | s->smb_blkdata = s->smb_data[s->smb_index]; | |
302 | } | |
38ad4fae CM |
303 | s->smb_index = 0; |
304 | s->smb_stat |= STS_INTR; | |
305 | s->smb_stat &= ~STS_HOST_BUSY; | |
306 | } else { | |
52cc6a49 CM |
307 | if (s->in_i2c_block_read) { |
308 | s->smb_blkdata = i2c_recv(s->smbus); | |
309 | } else { | |
310 | s->smb_blkdata = s->smb_data[s->smb_index]; | |
311 | } | |
38ad4fae CM |
312 | s->smb_stat |= STS_BYTE_DONE; |
313 | } | |
314 | } | |
fc0bdd99 IY |
315 | break; |
316 | case SMBHSTCNT: | |
38ad4fae CM |
317 | s->smb_ctl = val & ~CTL_START; /* CTL_START always reads 0 */ |
318 | if (val & CTL_START) { | |
319 | if (!s->op_done) { | |
320 | s->smb_index = 0; | |
321 | s->op_done = true; | |
52cc6a49 CM |
322 | if (s->in_i2c_block_read) { |
323 | s->in_i2c_block_read = false; | |
324 | i2c_end_transfer(s->smbus); | |
325 | } | |
38ad4fae | 326 | } |
880b1ffe | 327 | smb_transaction_start(s); |
b8fb9043 | 328 | } |
38ad4fae CM |
329 | if (s->smb_ctl & CTL_KILL) { |
330 | s->op_done = true; | |
331 | s->smb_index = 0; | |
332 | s->smb_stat |= STS_FAILED; | |
333 | s->smb_stat &= ~STS_HOST_BUSY; | |
334 | } | |
fc0bdd99 IY |
335 | break; |
336 | case SMBHSTCMD: | |
337 | s->smb_cmd = val; | |
338 | break; | |
339 | case SMBHSTADD: | |
340 | s->smb_addr = val; | |
341 | break; | |
342 | case SMBHSTDAT0: | |
343 | s->smb_data0 = val; | |
344 | break; | |
345 | case SMBHSTDAT1: | |
346 | s->smb_data1 = val; | |
347 | break; | |
348 | case SMBBLKDAT: | |
38ad4fae | 349 | if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) { |
fc0bdd99 | 350 | s->smb_index = 0; |
38ad4fae CM |
351 | } |
352 | if (s->smb_auxctl & AUX_BLK) { | |
353 | s->smb_data[s->smb_index++] = val; | |
354 | } else { | |
355 | s->smb_blkdata = val; | |
356 | } | |
357 | break; | |
358 | case SMBAUXCTL: | |
359 | s->smb_auxctl = val & AUX_MASK; | |
fc0bdd99 IY |
360 | break; |
361 | default: | |
362 | break; | |
363 | } | |
38ad4fae CM |
364 | |
365 | out: | |
e724385a CM |
366 | if (s->set_irq) { |
367 | s->set_irq(s, smb_irq_value(s)); | |
368 | } | |
fc0bdd99 IY |
369 | } |
370 | ||
798512e5 | 371 | static uint64_t smb_ioport_readb(void *opaque, hwaddr addr, unsigned width) |
fc0bdd99 IY |
372 | { |
373 | PMSMBus *s = opaque; | |
374 | uint32_t val; | |
375 | ||
fc0bdd99 IY |
376 | switch(addr) { |
377 | case SMBHSTSTS: | |
378 | val = s->smb_stat; | |
52cc6a49 | 379 | if (s->start_transaction_on_status_read) { |
880b1ffe | 380 | /* execute command now */ |
52cc6a49 | 381 | s->start_transaction_on_status_read = false; |
12bd93c1 | 382 | s->smb_stat &= ~STS_HOST_BUSY; |
880b1ffe HP |
383 | smb_transaction(s); |
384 | } | |
fc0bdd99 IY |
385 | break; |
386 | case SMBHSTCNT: | |
b8fb9043 | 387 | val = s->smb_ctl & CTL_RETURN_MASK; |
fc0bdd99 IY |
388 | break; |
389 | case SMBHSTCMD: | |
390 | val = s->smb_cmd; | |
391 | break; | |
392 | case SMBHSTADD: | |
393 | val = s->smb_addr; | |
394 | break; | |
395 | case SMBHSTDAT0: | |
396 | val = s->smb_data0; | |
397 | break; | |
398 | case SMBHSTDAT1: | |
399 | val = s->smb_data1; | |
400 | break; | |
401 | case SMBBLKDAT: | |
52cc6a49 CM |
402 | if (s->smb_auxctl & AUX_BLK && !s->in_i2c_block_read) { |
403 | if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) { | |
404 | s->smb_index = 0; | |
405 | } | |
38ad4fae CM |
406 | val = s->smb_data[s->smb_index++]; |
407 | if (!s->op_done && s->smb_index == s->smb_data0) { | |
408 | s->op_done = true; | |
409 | s->smb_index = 0; | |
410 | s->smb_stat &= ~STS_HOST_BUSY; | |
411 | } | |
412 | } else { | |
413 | val = s->smb_blkdata; | |
414 | } | |
415 | break; | |
416 | case SMBAUXCTL: | |
417 | val = s->smb_auxctl; | |
fc0bdd99 IY |
418 | break; |
419 | default: | |
420 | val = 0; | |
421 | break; | |
422 | } | |
c6e1b31b | 423 | trace_smbus_ioport_readb(addr, val); |
b8fb9043 | 424 | |
e724385a CM |
425 | if (s->set_irq) { |
426 | s->set_irq(s, smb_irq_value(s)); | |
427 | } | |
428 | ||
fc0bdd99 IY |
429 | return val; |
430 | } | |
431 | ||
38ad4fae CM |
432 | static void pm_smbus_reset(PMSMBus *s) |
433 | { | |
434 | s->op_done = true; | |
435 | s->smb_index = 0; | |
436 | s->smb_stat = 0; | |
437 | } | |
438 | ||
798512e5 GH |
439 | static const MemoryRegionOps pm_smbus_ops = { |
440 | .read = smb_ioport_readb, | |
441 | .write = smb_ioport_writeb, | |
442 | .valid.min_access_size = 1, | |
443 | .valid.max_access_size = 1, | |
444 | .endianness = DEVICE_LITTLE_ENDIAN, | |
445 | }; | |
446 | ||
4ab2f2a8 CM |
447 | bool pm_smbus_vmstate_needed(void) |
448 | { | |
449 | MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); | |
450 | ||
451 | return !mc->smbus_no_migration_support; | |
452 | } | |
453 | ||
454 | const VMStateDescription pmsmb_vmstate = { | |
455 | .name = "pmsmb", | |
456 | .version_id = 1, | |
457 | .minimum_version_id = 1, | |
01d9442a | 458 | .fields = (const VMStateField[]) { |
4ab2f2a8 CM |
459 | VMSTATE_UINT8(smb_stat, PMSMBus), |
460 | VMSTATE_UINT8(smb_ctl, PMSMBus), | |
461 | VMSTATE_UINT8(smb_cmd, PMSMBus), | |
462 | VMSTATE_UINT8(smb_addr, PMSMBus), | |
463 | VMSTATE_UINT8(smb_data0, PMSMBus), | |
464 | VMSTATE_UINT8(smb_data1, PMSMBus), | |
465 | VMSTATE_UINT32(smb_index, PMSMBus), | |
466 | VMSTATE_UINT8_ARRAY(smb_data, PMSMBus, PM_SMBUS_MAX_MSG_SIZE), | |
467 | VMSTATE_UINT8(smb_auxctl, PMSMBus), | |
468 | VMSTATE_UINT8(smb_blkdata, PMSMBus), | |
469 | VMSTATE_BOOL(i2c_enable, PMSMBus), | |
470 | VMSTATE_BOOL(op_done, PMSMBus), | |
471 | VMSTATE_BOOL(in_i2c_block_read, PMSMBus), | |
472 | VMSTATE_BOOL(start_transaction_on_status_read, PMSMBus), | |
473 | VMSTATE_END_OF_LIST() | |
474 | } | |
475 | }; | |
476 | ||
45726b6e | 477 | void pm_smbus_init(DeviceState *parent, PMSMBus *smb, bool force_aux_blk) |
fc0bdd99 | 478 | { |
38ad4fae CM |
479 | smb->op_done = true; |
480 | smb->reset = pm_smbus_reset; | |
fc0bdd99 | 481 | smb->smbus = i2c_init_bus(parent, "i2c"); |
45726b6e CM |
482 | if (force_aux_blk) { |
483 | smb->smb_auxctl |= AUX_BLK; | |
484 | } | |
1437c94b PB |
485 | memory_region_init_io(&smb->io, OBJECT(parent), &pm_smbus_ops, smb, |
486 | "pm-smbus", 64); | |
fc0bdd99 | 487 | } |