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