2 * PCA9552 I2C LED blinker
4 * https://www.nxp.com/docs/en/application-note/AN264.pdf
6 * Copyright (c) 2017-2018, IBM Corporation.
7 * Copyright (c) 2020 Philippe Mathieu-Daudé
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.
13 #include "qemu/osdep.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"
23 typedef struct PCA955xClass
{
25 I2CSlaveClass parent_class
;
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)
37 #define PCA9552_LED_ON 0x0
38 #define PCA9552_LED_OFF 0x1
39 #define PCA9552_LED_PWM0 0x2
40 #define PCA9552_LED_PWM1 0x3
42 static const char *led_state
[] = {"on", "off", "pwm0", "pwm1"};
44 static uint8_t pca955x_pin_get_config(PCA955xState
*s
, int pin
)
46 uint8_t reg
= PCA9552_LS0
+ (pin
/ 4);
47 uint8_t shift
= (pin
% 4) << 1;
49 return extract32(s
->regs
[reg
], shift
, 2);
52 static void pca955x_update_pin_input(PCA955xState
*s
)
54 PCA955xClass
*k
= PCA955X_GET_CLASS(s
);
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
);
64 s
->regs
[input_reg
] |= 1 << input_shift
;
67 s
->regs
[input_reg
] &= ~(1 << input_shift
);
69 case PCA9552_LED_PWM0
:
70 case PCA9552_LED_PWM1
:
78 static uint8_t pca955x_read(PCA955xState
*s
, uint8_t reg
)
93 qemu_log_mask(LOG_GUEST_ERROR
, "%s: unexpected read to register %d\n",
99 static void pca955x_write(PCA955xState
*s
, uint8_t reg
, uint8_t data
)
114 pca955x_update_pin_input(s
);
120 qemu_log_mask(LOG_GUEST_ERROR
, "%s: unexpected write to register %d\n",
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.
130 static void pca955x_autoinc(PCA955xState
*s
)
132 PCA955xClass
*k
= PCA955X_GET_CLASS(s
);
134 if (s
->pointer
!= 0xFF && s
->pointer
& PCA9552_AUTOINC
) {
135 uint8_t reg
= s
->pointer
& 0xf;
137 reg
= (reg
+ 1) % (k
->max_reg
+ 1);
138 s
->pointer
= reg
| PCA9552_AUTOINC
;
142 static uint8_t pca955x_recv(I2CSlave
*i2c
)
144 PCA955xState
*s
= PCA955X(i2c
);
147 ret
= pca955x_read(s
, s
->pointer
& 0xf);
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.
156 * I don't know what should be done in this case, so throw an
159 if (s
->pointer
== PCA9552_AUTOINC
) {
160 qemu_log_mask(LOG_GUEST_ERROR
,
161 "%s: Autoincrement read starting with register 0\n",
170 static int pca955x_send(I2CSlave
*i2c
, uint8_t data
)
172 PCA955xState
*s
= PCA955X(i2c
);
174 /* First byte sent by is the register address */
179 pca955x_write(s
, s
->pointer
& 0xf, data
);
187 static int pca955x_event(I2CSlave
*i2c
, enum i2c_event event
)
189 PCA955xState
*s
= PCA955X(i2c
);
195 static void pca955x_get_led(Object
*obj
, Visitor
*v
, const char *name
,
196 void *opaque
, Error
**errp
)
198 PCA955xClass
*k
= PCA955X_GET_CLASS(obj
);
199 PCA955xState
*s
= PCA955X(obj
);
203 rc
= sscanf(name
, "led%2d", &led
);
205 error_setg(errp
, "%s: error reading %s", __func__
, name
);
208 if (led
< 0 || led
> k
->pin_count
) {
209 error_setg(errp
, "%s invalid led %s", __func__
, name
);
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
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
);
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).
226 static inline uint8_t pca955x_ledsel(uint8_t oldval
, int led_num
, int state
)
228 return (oldval
& (~(0x3 << (led_num
<< 1)))) |
229 ((state
& 0x3) << (led_num
<< 1));
232 static void pca955x_set_led(Object
*obj
, Visitor
*v
, const char *name
,
233 void *opaque
, Error
**errp
)
235 PCA955xClass
*k
= PCA955X_GET_CLASS(obj
);
236 PCA955xState
*s
= PCA955X(obj
);
237 Error
*local_err
= NULL
;
238 int led
, rc
, reg
, val
;
242 visit_type_str(v
, name
, &state_str
, &local_err
);
244 error_propagate(errp
, local_err
);
247 rc
= sscanf(name
, "led%2d", &led
);
249 error_setg(errp
, "%s: error reading %s", __func__
, name
);
252 if (led
< 0 || led
> k
->pin_count
) {
253 error_setg(errp
, "%s invalid led %s", __func__
, name
);
257 for (state
= 0; state
< ARRAY_SIZE(led_state
); state
++) {
258 if (!strcmp(state_str
, led_state
[state
])) {
262 if (state
>= ARRAY_SIZE(led_state
)) {
263 error_setg(errp
, "%s invalid led state %s", __func__
, state_str
);
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
);
273 static const VMStateDescription pca9552_vmstate
= {
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()
286 static void pca9552_reset(DeviceState
*dev
)
288 PCA955xState
*s
= PCA955X(dev
);
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;
299 pca955x_update_pin_input(s
);
305 static void pca955x_initfn(Object
*obj
)
307 PCA955xClass
*k
= PCA955X_GET_CLASS(obj
);
310 assert(k
->pin_count
<= PCA955X_PIN_COUNT_MAX
);
311 for (led
= 0; led
< k
->pin_count
; led
++) {
314 name
= g_strdup_printf("led%d", led
);
315 object_property_add(obj
, name
, "bool", pca955x_get_led
, pca955x_set_led
,
321 static void pca955x_realize(DeviceState
*dev
, Error
**errp
)
323 PCA955xState
*s
= PCA955X(dev
);
325 if (!s
->description
) {
326 s
->description
= g_strdup("pca-unspecified");
330 static Property pca955x_properties
[] = {
331 DEFINE_PROP_STRING("description", PCA955xState
, description
),
332 DEFINE_PROP_END_OF_LIST(),
335 static void pca955x_class_init(ObjectClass
*klass
, void *data
)
337 DeviceClass
*dc
= DEVICE_CLASS(klass
);
338 I2CSlaveClass
*k
= I2C_SLAVE_CLASS(klass
);
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
);
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
,
356 static void pca9552_class_init(ObjectClass
*oc
, void *data
)
358 DeviceClass
*dc
= DEVICE_CLASS(oc
);
359 PCA955xClass
*pc
= PCA955X_CLASS(oc
);
361 dc
->reset
= pca9552_reset
;
362 dc
->vmsd
= &pca9552_vmstate
;
363 pc
->max_reg
= PCA9552_LS3
;
367 static const TypeInfo pca9552_info
= {
368 .name
= TYPE_PCA9552
,
369 .parent
= TYPE_PCA955X
,
370 .class_init
= pca9552_class_init
,
373 static void pca955x_register_types(void)
375 type_register_static(&pca955x_info
);
376 type_register_static(&pca9552_info
);
379 type_init(pca955x_register_types
)