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