1 | /* |
2 | * PCA9552 I2C LED blinker |
3 | * |
4 | * https://www.nxp.com/docs/en/application-note/AN264.pdf |
5 | * |
6 | * Copyright (c) 2017-2018, IBM Corporation. |
7 | * |
8 | * This work is licensed under the terms of the GNU GPL, version 2 or |
9 | * later. See the COPYING file in the top-level directory. |
10 | */ |
11 | |
12 | #include "qemu/osdep.h" |
13 | #include "qemu/log.h" |
14 | #include "qemu/module.h" |
15 | #include "hw/misc/pca9552.h" |
16 | #include "hw/misc/pca9552_regs.h" |
17 | #include "migration/vmstate.h" |
18 | |
19 | #define PCA9552_LED_ON 0x0 |
20 | #define PCA9552_LED_OFF 0x1 |
21 | #define PCA9552_LED_PWM0 0x2 |
22 | #define PCA9552_LED_PWM1 0x3 |
23 | |
24 | static uint8_t pca9552_pin_get_config(PCA9552State *s, int pin) |
25 | { |
26 | uint8_t reg = PCA9552_LS0 + (pin / 4); |
27 | uint8_t shift = (pin % 4) << 1; |
28 | |
29 | return extract32(s->regs[reg], shift, 2); |
30 | } |
31 | |
32 | static void pca9552_update_pin_input(PCA9552State *s) |
33 | { |
34 | int i; |
35 | |
36 | for (i = 0; i < s->nr_leds; i++) { |
37 | uint8_t input_reg = PCA9552_INPUT0 + (i / 8); |
38 | uint8_t input_shift = (i % 8); |
39 | uint8_t config = pca9552_pin_get_config(s, i); |
40 | |
41 | switch (config) { |
42 | case PCA9552_LED_ON: |
43 | s->regs[input_reg] |= 1 << input_shift; |
44 | break; |
45 | case PCA9552_LED_OFF: |
46 | s->regs[input_reg] &= ~(1 << input_shift); |
47 | break; |
48 | case PCA9552_LED_PWM0: |
49 | case PCA9552_LED_PWM1: |
50 | /* TODO */ |
51 | default: |
52 | break; |
53 | } |
54 | } |
55 | } |
56 | |
57 | static uint8_t pca9552_read(PCA9552State *s, uint8_t reg) |
58 | { |
59 | switch (reg) { |
60 | case PCA9552_INPUT0: |
61 | case PCA9552_INPUT1: |
62 | case PCA9552_PSC0: |
63 | case PCA9552_PWM0: |
64 | case PCA9552_PSC1: |
65 | case PCA9552_PWM1: |
66 | case PCA9552_LS0: |
67 | case PCA9552_LS1: |
68 | case PCA9552_LS2: |
69 | case PCA9552_LS3: |
70 | return s->regs[reg]; |
71 | default: |
72 | qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n" , |
73 | __func__, reg); |
74 | return 0xFF; |
75 | } |
76 | } |
77 | |
78 | static void pca9552_write(PCA9552State *s, uint8_t reg, uint8_t data) |
79 | { |
80 | switch (reg) { |
81 | case PCA9552_PSC0: |
82 | case PCA9552_PWM0: |
83 | case PCA9552_PSC1: |
84 | case PCA9552_PWM1: |
85 | s->regs[reg] = data; |
86 | break; |
87 | |
88 | case PCA9552_LS0: |
89 | case PCA9552_LS1: |
90 | case PCA9552_LS2: |
91 | case PCA9552_LS3: |
92 | s->regs[reg] = data; |
93 | pca9552_update_pin_input(s); |
94 | break; |
95 | |
96 | case PCA9552_INPUT0: |
97 | case PCA9552_INPUT1: |
98 | default: |
99 | qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n" , |
100 | __func__, reg); |
101 | } |
102 | } |
103 | |
104 | /* |
105 | * When Auto-Increment is on, the register address is incremented |
106 | * after each byte is sent to or received by the device. The index |
107 | * rollovers to 0 when the maximum register address is reached. |
108 | */ |
109 | static void pca9552_autoinc(PCA9552State *s) |
110 | { |
111 | if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) { |
112 | uint8_t reg = s->pointer & 0xf; |
113 | |
114 | reg = (reg + 1) % (s->max_reg + 1); |
115 | s->pointer = reg | PCA9552_AUTOINC; |
116 | } |
117 | } |
118 | |
119 | static uint8_t pca9552_recv(I2CSlave *i2c) |
120 | { |
121 | PCA9552State *s = PCA9552(i2c); |
122 | uint8_t ret; |
123 | |
124 | ret = pca9552_read(s, s->pointer & 0xf); |
125 | |
126 | /* |
127 | * From the Specs: |
128 | * |
129 | * Important Note: When a Read sequence is initiated and the |
130 | * AI bit is set to Logic Level 1, the Read Sequence MUST |
131 | * start by a register different from 0. |
132 | * |
133 | * I don't know what should be done in this case, so throw an |
134 | * error. |
135 | */ |
136 | if (s->pointer == PCA9552_AUTOINC) { |
137 | qemu_log_mask(LOG_GUEST_ERROR, |
138 | "%s: Autoincrement read starting with register 0\n" , |
139 | __func__); |
140 | } |
141 | |
142 | pca9552_autoinc(s); |
143 | |
144 | return ret; |
145 | } |
146 | |
147 | static int pca9552_send(I2CSlave *i2c, uint8_t data) |
148 | { |
149 | PCA9552State *s = PCA9552(i2c); |
150 | |
151 | /* First byte sent by is the register address */ |
152 | if (s->len == 0) { |
153 | s->pointer = data; |
154 | s->len++; |
155 | } else { |
156 | pca9552_write(s, s->pointer & 0xf, data); |
157 | |
158 | pca9552_autoinc(s); |
159 | } |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | static int pca9552_event(I2CSlave *i2c, enum i2c_event event) |
165 | { |
166 | PCA9552State *s = PCA9552(i2c); |
167 | |
168 | s->len = 0; |
169 | return 0; |
170 | } |
171 | |
172 | static const VMStateDescription pca9552_vmstate = { |
173 | .name = "PCA9552" , |
174 | .version_id = 0, |
175 | .minimum_version_id = 0, |
176 | .fields = (VMStateField[]) { |
177 | VMSTATE_UINT8(len, PCA9552State), |
178 | VMSTATE_UINT8(pointer, PCA9552State), |
179 | VMSTATE_UINT8_ARRAY(regs, PCA9552State, PCA9552_NR_REGS), |
180 | VMSTATE_I2C_SLAVE(i2c, PCA9552State), |
181 | VMSTATE_END_OF_LIST() |
182 | } |
183 | }; |
184 | |
185 | static void pca9552_reset(DeviceState *dev) |
186 | { |
187 | PCA9552State *s = PCA9552(dev); |
188 | |
189 | s->regs[PCA9552_PSC0] = 0xFF; |
190 | s->regs[PCA9552_PWM0] = 0x80; |
191 | s->regs[PCA9552_PSC1] = 0xFF; |
192 | s->regs[PCA9552_PWM1] = 0x80; |
193 | s->regs[PCA9552_LS0] = 0x55; /* all OFF */ |
194 | s->regs[PCA9552_LS1] = 0x55; |
195 | s->regs[PCA9552_LS2] = 0x55; |
196 | s->regs[PCA9552_LS3] = 0x55; |
197 | |
198 | pca9552_update_pin_input(s); |
199 | |
200 | s->pointer = 0xFF; |
201 | s->len = 0; |
202 | } |
203 | |
204 | static void pca9552_initfn(Object *obj) |
205 | { |
206 | PCA9552State *s = PCA9552(obj); |
207 | |
208 | /* If support for the other PCA955X devices are implemented, these |
209 | * constant values might be part of class structure describing the |
210 | * PCA955X device |
211 | */ |
212 | s->max_reg = PCA9552_LS3; |
213 | s->nr_leds = 16; |
214 | } |
215 | |
216 | static void pca9552_class_init(ObjectClass *klass, void *data) |
217 | { |
218 | DeviceClass *dc = DEVICE_CLASS(klass); |
219 | I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); |
220 | |
221 | k->event = pca9552_event; |
222 | k->recv = pca9552_recv; |
223 | k->send = pca9552_send; |
224 | dc->reset = pca9552_reset; |
225 | dc->vmsd = &pca9552_vmstate; |
226 | } |
227 | |
228 | static const TypeInfo pca9552_info = { |
229 | .name = TYPE_PCA9552, |
230 | .parent = TYPE_I2C_SLAVE, |
231 | .instance_init = pca9552_initfn, |
232 | .instance_size = sizeof(PCA9552State), |
233 | .class_init = pca9552_class_init, |
234 | }; |
235 | |
236 | static void pca9552_register_types(void) |
237 | { |
238 | type_register_static(&pca9552_info); |
239 | } |
240 | |
241 | type_init(pca9552_register_types) |
242 | |