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 * Copyright (c) 2020 Philippe Mathieu-Daudé 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or 10 * later. See the COPYING file in the top-level directory. 11 */ 12 13 #include "qemu/osdep.h" 14 #include "qemu/log.h" 15 #include "qemu/module.h" 16 #include "hw/misc/pca9552.h" 17 #include "hw/misc/pca9552_regs.h" 18 #include "migration/vmstate.h" 19 #include "qapi/error.h" 20 #include "qapi/visitor.h" 21 22 typedef struct PCA955xClass { 23 /*< private >*/ 24 I2CSlaveClass parent_class; 25 /*< public >*/ 26 27 uint8_t pin_count; 28 uint8_t max_reg; 29 } PCA955xClass; 30 31 #define PCA955X_CLASS(klass) \ 32 OBJECT_CLASS_CHECK(PCA955xClass, (klass), TYPE_PCA955X) 33 #define PCA955X_GET_CLASS(obj) \ 34 OBJECT_GET_CLASS(PCA955xClass, (obj), TYPE_PCA955X) 35 36 #define PCA9552_LED_ON 0x0 37 #define PCA9552_LED_OFF 0x1 38 #define PCA9552_LED_PWM0 0x2 39 #define PCA9552_LED_PWM1 0x3 40 41 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"}; 42 43 static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin) 44 { 45 uint8_t reg = PCA9552_LS0 + (pin / 4); 46 uint8_t shift = (pin % 4) << 1; 47 48 return extract32(s->regs[reg], shift, 2); 49 } 50 51 static void pca955x_update_pin_input(PCA955xState *s) 52 { 53 PCA955xClass *k = PCA955X_GET_CLASS(s); 54 int i; 55 56 for (i = 0; i < k->pin_count; i++) { 57 uint8_t input_reg = PCA9552_INPUT0 + (i / 8); 58 uint8_t input_shift = (i % 8); 59 uint8_t config = pca955x_pin_get_config(s, i); 60 61 switch (config) { 62 case PCA9552_LED_ON: 63 s->regs[input_reg] |= 1 << input_shift; 64 break; 65 case PCA9552_LED_OFF: 66 s->regs[input_reg] &= ~(1 << input_shift); 67 break; 68 case PCA9552_LED_PWM0: 69 case PCA9552_LED_PWM1: 70 /* TODO */ 71 default: 72 break; 73 } 74 } 75 } 76 77 static uint8_t pca955x_read(PCA955xState *s, uint8_t reg) 78 { 79 switch (reg) { 80 case PCA9552_INPUT0: 81 case PCA9552_INPUT1: 82 case PCA9552_PSC0: 83 case PCA9552_PWM0: 84 case PCA9552_PSC1: 85 case PCA9552_PWM1: 86 case PCA9552_LS0: 87 case PCA9552_LS1: 88 case PCA9552_LS2: 89 case PCA9552_LS3: 90 return s->regs[reg]; 91 default: 92 qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n", 93 __func__, reg); 94 return 0xFF; 95 } 96 } 97 98 static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data) 99 { 100 switch (reg) { 101 case PCA9552_PSC0: 102 case PCA9552_PWM0: 103 case PCA9552_PSC1: 104 case PCA9552_PWM1: 105 s->regs[reg] = data; 106 break; 107 108 case PCA9552_LS0: 109 case PCA9552_LS1: 110 case PCA9552_LS2: 111 case PCA9552_LS3: 112 s->regs[reg] = data; 113 pca955x_update_pin_input(s); 114 break; 115 116 case PCA9552_INPUT0: 117 case PCA9552_INPUT1: 118 default: 119 qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n", 120 __func__, reg); 121 } 122 } 123 124 /* 125 * When Auto-Increment is on, the register address is incremented 126 * after each byte is sent to or received by the device. The index 127 * rollovers to 0 when the maximum register address is reached. 128 */ 129 static void pca955x_autoinc(PCA955xState *s) 130 { 131 PCA955xClass *k = PCA955X_GET_CLASS(s); 132 133 if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) { 134 uint8_t reg = s->pointer & 0xf; 135 136 reg = (reg + 1) % (k->max_reg + 1); 137 s->pointer = reg | PCA9552_AUTOINC; 138 } 139 } 140 141 static uint8_t pca955x_recv(I2CSlave *i2c) 142 { 143 PCA955xState *s = PCA955X(i2c); 144 uint8_t ret; 145 146 ret = pca955x_read(s, s->pointer & 0xf); 147 148 /* 149 * From the Specs: 150 * 151 * Important Note: When a Read sequence is initiated and the 152 * AI bit is set to Logic Level 1, the Read Sequence MUST 153 * start by a register different from 0. 154 * 155 * I don't know what should be done in this case, so throw an 156 * error. 157 */ 158 if (s->pointer == PCA9552_AUTOINC) { 159 qemu_log_mask(LOG_GUEST_ERROR, 160 "%s: Autoincrement read starting with register 0\n", 161 __func__); 162 } 163 164 pca955x_autoinc(s); 165 166 return ret; 167 } 168 169 static int pca955x_send(I2CSlave *i2c, uint8_t data) 170 { 171 PCA955xState *s = PCA955X(i2c); 172 173 /* First byte sent by is the register address */ 174 if (s->len == 0) { 175 s->pointer = data; 176 s->len++; 177 } else { 178 pca955x_write(s, s->pointer & 0xf, data); 179 180 pca955x_autoinc(s); 181 } 182 183 return 0; 184 } 185 186 static int pca955x_event(I2CSlave *i2c, enum i2c_event event) 187 { 188 PCA955xState *s = PCA955X(i2c); 189 190 s->len = 0; 191 return 0; 192 } 193 194 static void pca955x_get_led(Object *obj, Visitor *v, const char *name, 195 void *opaque, Error **errp) 196 { 197 PCA955xClass *k = PCA955X_GET_CLASS(obj); 198 PCA955xState *s = PCA955X(obj); 199 int led, rc, reg; 200 uint8_t state; 201 202 rc = sscanf(name, "led%2d", &led); 203 if (rc != 1) { 204 error_setg(errp, "%s: error reading %s", __func__, name); 205 return; 206 } 207 if (led < 0 || led > k->pin_count) { 208 error_setg(errp, "%s invalid led %s", __func__, name); 209 return; 210 } 211 /* 212 * Get the LSx register as the qom interface should expose the device 213 * state, not the modeled 'input line' behaviour which would come from 214 * reading the INPUTx reg 215 */ 216 reg = PCA9552_LS0 + led / 4; 217 state = (pca955x_read(s, reg) >> (led % 8)) & 0x3; 218 visit_type_str(v, name, (char **)&led_state[state], errp); 219 } 220 221 /* 222 * Return an LED selector register value based on an existing one, with 223 * the appropriate 2-bit state value set for the given LED number (0-3). 224 */ 225 static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state) 226 { 227 return (oldval & (~(0x3 << (led_num << 1)))) | 228 ((state & 0x3) << (led_num << 1)); 229 } 230 231 static void pca955x_set_led(Object *obj, Visitor *v, const char *name, 232 void *opaque, Error **errp) 233 { 234 PCA955xClass *k = PCA955X_GET_CLASS(obj); 235 PCA955xState *s = PCA955X(obj); 236 Error *local_err = NULL; 237 int led, rc, reg, val; 238 uint8_t state; 239 char *state_str; 240 241 visit_type_str(v, name, &state_str, &local_err); 242 if (local_err) { 243 error_propagate(errp, local_err); 244 return; 245 } 246 rc = sscanf(name, "led%2d", &led); 247 if (rc != 1) { 248 error_setg(errp, "%s: error reading %s", __func__, name); 249 return; 250 } 251 if (led < 0 || led > k->pin_count) { 252 error_setg(errp, "%s invalid led %s", __func__, name); 253 return; 254 } 255 256 for (state = 0; state < ARRAY_SIZE(led_state); state++) { 257 if (!strcmp(state_str, led_state[state])) { 258 break; 259 } 260 } 261 if (state >= ARRAY_SIZE(led_state)) { 262 error_setg(errp, "%s invalid led state %s", __func__, state_str); 263 return; 264 } 265 266 reg = PCA9552_LS0 + led / 4; 267 val = pca955x_read(s, reg); 268 val = pca955x_ledsel(val, led % 4, state); 269 pca955x_write(s, reg, val); 270 } 271 272 static const VMStateDescription pca9552_vmstate = { 273 .name = "PCA9552", 274 .version_id = 0, 275 .minimum_version_id = 0, 276 .fields = (VMStateField[]) { 277 VMSTATE_UINT8(len, PCA955xState), 278 VMSTATE_UINT8(pointer, PCA955xState), 279 VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS), 280 VMSTATE_I2C_SLAVE(i2c, PCA955xState), 281 VMSTATE_END_OF_LIST() 282 } 283 }; 284 285 static void pca9552_reset(DeviceState *dev) 286 { 287 PCA955xState *s = PCA955X(dev); 288 289 s->regs[PCA9552_PSC0] = 0xFF; 290 s->regs[PCA9552_PWM0] = 0x80; 291 s->regs[PCA9552_PSC1] = 0xFF; 292 s->regs[PCA9552_PWM1] = 0x80; 293 s->regs[PCA9552_LS0] = 0x55; /* all OFF */ 294 s->regs[PCA9552_LS1] = 0x55; 295 s->regs[PCA9552_LS2] = 0x55; 296 s->regs[PCA9552_LS3] = 0x55; 297 298 pca955x_update_pin_input(s); 299 300 s->pointer = 0xFF; 301 s->len = 0; 302 } 303 304 static void pca955x_initfn(Object *obj) 305 { 306 PCA955xClass *k = PCA955X_GET_CLASS(obj); 307 int led; 308 309 assert(k->pin_count <= PCA955X_PIN_COUNT_MAX); 310 for (led = 0; led < k->pin_count; led++) { 311 char *name; 312 313 name = g_strdup_printf("led%d", led); 314 object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led, 315 NULL, NULL); 316 g_free(name); 317 } 318 } 319 320 static void pca955x_class_init(ObjectClass *klass, void *data) 321 { 322 I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); 323 324 k->event = pca955x_event; 325 k->recv = pca955x_recv; 326 k->send = pca955x_send; 327 } 328 329 static const TypeInfo pca955x_info = { 330 .name = TYPE_PCA955X, 331 .parent = TYPE_I2C_SLAVE, 332 .instance_init = pca955x_initfn, 333 .instance_size = sizeof(PCA955xState), 334 .class_init = pca955x_class_init, 335 .abstract = true, 336 }; 337 338 static void pca9552_class_init(ObjectClass *oc, void *data) 339 { 340 DeviceClass *dc = DEVICE_CLASS(oc); 341 PCA955xClass *pc = PCA955X_CLASS(oc); 342 343 dc->reset = pca9552_reset; 344 dc->vmsd = &pca9552_vmstate; 345 pc->max_reg = PCA9552_LS3; 346 pc->pin_count = 16; 347 } 348 349 static const TypeInfo pca9552_info = { 350 .name = TYPE_PCA9552, 351 .parent = TYPE_PCA955X, 352 .class_init = pca9552_class_init, 353 }; 354 355 static void pca955x_register_types(void) 356 { 357 type_register_static(&pca955x_info); 358 type_register_static(&pca9552_info); 359 } 360 361 type_init(pca955x_register_types) 362