2018-06-08 15:15:32 +03:00
|
|
|
/*
|
|
|
|
* PCA9552 I2C LED blinker
|
|
|
|
*
|
|
|
|
* https://www.nxp.com/docs/en/application-note/AN264.pdf
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017-2018, IBM Corporation.
|
2020-06-23 10:27:18 +03:00
|
|
|
* Copyright (c) 2020 Philippe Mathieu-Daudé
|
2018-06-08 15:15:32 +03:00
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or
|
|
|
|
* later. See the COPYING file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "qemu/log.h"
|
2019-05-23 17:35:07 +03:00
|
|
|
#include "qemu/module.h"
|
2020-06-23 10:27:20 +03:00
|
|
|
#include "qemu/bitops.h"
|
2020-06-23 10:27:19 +03:00
|
|
|
#include "hw/qdev-properties.h"
|
2024-03-25 16:48:32 +03:00
|
|
|
#include "hw/gpio/pca9552.h"
|
|
|
|
#include "hw/gpio/pca9552_regs.h"
|
2020-06-23 10:27:23 +03:00
|
|
|
#include "hw/irq.h"
|
2019-08-12 08:23:45 +03:00
|
|
|
#include "migration/vmstate.h"
|
2020-01-30 19:02:02 +03:00
|
|
|
#include "qapi/error.h"
|
|
|
|
#include "qapi/visitor.h"
|
2020-06-23 10:27:20 +03:00
|
|
|
#include "trace.h"
|
2020-09-03 23:43:22 +03:00
|
|
|
#include "qom/object.h"
|
2018-06-08 15:15:32 +03:00
|
|
|
|
2020-09-03 23:43:22 +03:00
|
|
|
struct PCA955xClass {
|
2020-06-23 10:27:18 +03:00
|
|
|
/*< private >*/
|
|
|
|
I2CSlaveClass parent_class;
|
|
|
|
/*< public >*/
|
|
|
|
|
|
|
|
uint8_t pin_count;
|
|
|
|
uint8_t max_reg;
|
2020-09-03 23:43:22 +03:00
|
|
|
};
|
|
|
|
typedef struct PCA955xClass PCA955xClass;
|
2020-06-23 10:27:18 +03:00
|
|
|
|
2020-09-01 00:07:33 +03:00
|
|
|
DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
|
|
|
|
TYPE_PCA955X)
|
2024-01-26 01:48:10 +03:00
|
|
|
/*
|
|
|
|
* Note: The LED_ON and LED_OFF configuration values for the PCA955X
|
|
|
|
* chips are the reverse of the PCA953X family of chips.
|
|
|
|
*/
|
2018-06-08 15:15:32 +03:00
|
|
|
#define PCA9552_LED_ON 0x0
|
|
|
|
#define PCA9552_LED_OFF 0x1
|
|
|
|
#define PCA9552_LED_PWM0 0x2
|
|
|
|
#define PCA9552_LED_PWM1 0x3
|
2024-02-05 10:40:15 +03:00
|
|
|
#define PCA9552_PIN_LOW 0x0
|
|
|
|
#define PCA9552_PIN_HIZ 0x1
|
2018-06-08 15:15:32 +03:00
|
|
|
|
2020-01-30 19:02:02 +03:00
|
|
|
static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
|
|
|
uint8_t reg = PCA9552_LS0 + (pin / 4);
|
|
|
|
uint8_t shift = (pin % 4) << 1;
|
|
|
|
|
|
|
|
return extract32(s->regs[reg], shift, 2);
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:20 +03:00
|
|
|
/* Return INPUT status (bit #N belongs to GPIO #N) */
|
|
|
|
static uint16_t pca955x_pins_get_status(PCA955xState *s)
|
|
|
|
{
|
|
|
|
return (s->regs[PCA9552_INPUT1] << 8) | s->regs[PCA9552_INPUT0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pca955x_display_pins_status(PCA955xState *s,
|
|
|
|
uint16_t previous_pins_status)
|
|
|
|
{
|
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(s);
|
|
|
|
uint16_t pins_status, pins_changed;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
pins_status = pca955x_pins_get_status(s);
|
|
|
|
pins_changed = previous_pins_status ^ pins_status;
|
|
|
|
if (!pins_changed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_STATUS)) {
|
|
|
|
char *buf = g_newa(char, k->pin_count + 1);
|
|
|
|
|
|
|
|
for (i = 0; i < k->pin_count; i++) {
|
|
|
|
if (extract32(pins_status, i, 1)) {
|
|
|
|
buf[i] = '*';
|
|
|
|
} else {
|
|
|
|
buf[i] = '.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf[i] = '\0';
|
|
|
|
trace_pca955x_gpio_status(s->description, buf);
|
|
|
|
}
|
2020-06-23 10:27:22 +03:00
|
|
|
if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_CHANGE)) {
|
|
|
|
for (i = 0; i < k->pin_count; i++) {
|
|
|
|
if (extract32(pins_changed, i, 1)) {
|
|
|
|
unsigned new_state = extract32(pins_status, i, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We display the state using the PCA logic ("active-high").
|
|
|
|
* This is not the state of the LED, which signal might be
|
|
|
|
* wired "active-low" on the board.
|
|
|
|
*/
|
|
|
|
trace_pca955x_gpio_change(s->description, i,
|
|
|
|
!new_state, new_state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 10:27:20 +03:00
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_update_pin_input(PCA955xState *s)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(s);
|
2018-06-08 15:15:32 +03:00
|
|
|
int i;
|
|
|
|
|
2020-06-23 10:27:18 +03:00
|
|
|
for (i = 0; i < k->pin_count; i++) {
|
2018-06-08 15:15:32 +03:00
|
|
|
uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
|
2024-02-05 10:40:15 +03:00
|
|
|
uint8_t bit_mask = 1 << (i % 8);
|
2020-06-23 10:27:17 +03:00
|
|
|
uint8_t config = pca955x_pin_get_config(s, i);
|
2024-02-05 10:40:15 +03:00
|
|
|
uint8_t old_value = s->regs[input_reg] & bit_mask;
|
|
|
|
uint8_t new_value;
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
switch (config) {
|
|
|
|
case PCA9552_LED_ON:
|
2024-01-26 01:48:10 +03:00
|
|
|
/* Pin is set to 0V to turn on LED */
|
2024-02-05 10:40:15 +03:00
|
|
|
s->regs[input_reg] &= ~bit_mask;
|
2018-06-08 15:15:32 +03:00
|
|
|
break;
|
2024-01-26 01:48:10 +03:00
|
|
|
case PCA9552_LED_OFF:
|
|
|
|
/*
|
|
|
|
* Pin is set to Hi-Z to turn off LED and
|
2024-02-05 10:40:15 +03:00
|
|
|
* pullup sets it to a logical 1 unless
|
|
|
|
* external device drives it low.
|
2024-01-26 01:48:10 +03:00
|
|
|
*/
|
2024-02-05 10:40:15 +03:00
|
|
|
if (s->ext_state[i] == PCA9552_PIN_LOW) {
|
|
|
|
s->regs[input_reg] &= ~bit_mask;
|
|
|
|
} else {
|
|
|
|
s->regs[input_reg] |= bit_mask;
|
|
|
|
}
|
2024-01-26 01:48:10 +03:00
|
|
|
break;
|
2018-06-08 15:15:32 +03:00
|
|
|
case PCA9552_LED_PWM0:
|
|
|
|
case PCA9552_LED_PWM1:
|
|
|
|
/* TODO */
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2024-02-05 10:40:15 +03:00
|
|
|
|
|
|
|
/* update irq state only if pin state changed */
|
|
|
|
new_value = s->regs[input_reg] & bit_mask;
|
|
|
|
if (new_value != old_value) {
|
|
|
|
qemu_set_irq(s->gpio_out[i], !!new_value);
|
|
|
|
}
|
2018-06-08 15:15:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
|
|
|
switch (reg) {
|
|
|
|
case PCA9552_INPUT0:
|
|
|
|
case PCA9552_INPUT1:
|
|
|
|
case PCA9552_PSC0:
|
|
|
|
case PCA9552_PWM0:
|
|
|
|
case PCA9552_PSC1:
|
|
|
|
case PCA9552_PWM1:
|
|
|
|
case PCA9552_LS0:
|
|
|
|
case PCA9552_LS1:
|
|
|
|
case PCA9552_LS2:
|
|
|
|
case PCA9552_LS3:
|
|
|
|
return s->regs[reg];
|
|
|
|
default:
|
|
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
|
|
|
|
__func__, reg);
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:20 +03:00
|
|
|
uint16_t pins_status;
|
|
|
|
|
2018-06-08 15:15:32 +03:00
|
|
|
switch (reg) {
|
|
|
|
case PCA9552_PSC0:
|
|
|
|
case PCA9552_PWM0:
|
|
|
|
case PCA9552_PSC1:
|
|
|
|
case PCA9552_PWM1:
|
|
|
|
s->regs[reg] = data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PCA9552_LS0:
|
|
|
|
case PCA9552_LS1:
|
|
|
|
case PCA9552_LS2:
|
|
|
|
case PCA9552_LS3:
|
2020-06-23 10:27:20 +03:00
|
|
|
pins_status = pca955x_pins_get_status(s);
|
2018-06-08 15:15:32 +03:00
|
|
|
s->regs[reg] = data;
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_update_pin_input(s);
|
2020-06-23 10:27:20 +03:00
|
|
|
pca955x_display_pins_status(s, pins_status);
|
2018-06-08 15:15:32 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case PCA9552_INPUT0:
|
|
|
|
case PCA9552_INPUT1:
|
|
|
|
default:
|
|
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
|
|
|
|
__func__, reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When Auto-Increment is on, the register address is incremented
|
|
|
|
* after each byte is sent to or received by the device. The index
|
|
|
|
* rollovers to 0 when the maximum register address is reached.
|
|
|
|
*/
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_autoinc(PCA955xState *s)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(s);
|
|
|
|
|
2018-06-08 15:15:32 +03:00
|
|
|
if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
|
|
|
|
uint8_t reg = s->pointer & 0xf;
|
|
|
|
|
2020-06-23 10:27:18 +03:00
|
|
|
reg = (reg + 1) % (k->max_reg + 1);
|
2018-06-08 15:15:32 +03:00
|
|
|
s->pointer = reg | PCA9552_AUTOINC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static uint8_t pca955x_recv(I2CSlave *i2c)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(i2c);
|
2018-06-08 15:15:32 +03:00
|
|
|
uint8_t ret;
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
ret = pca955x_read(s, s->pointer & 0xf);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* From the Specs:
|
|
|
|
*
|
|
|
|
* Important Note: When a Read sequence is initiated and the
|
|
|
|
* AI bit is set to Logic Level 1, the Read Sequence MUST
|
|
|
|
* start by a register different from 0.
|
|
|
|
*
|
|
|
|
* I don't know what should be done in this case, so throw an
|
|
|
|
* error.
|
|
|
|
*/
|
|
|
|
if (s->pointer == PCA9552_AUTOINC) {
|
|
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
|
|
"%s: Autoincrement read starting with register 0\n",
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_autoinc(s);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static int pca955x_send(I2CSlave *i2c, uint8_t data)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(i2c);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
/* First byte sent by is the register address */
|
|
|
|
if (s->len == 0) {
|
|
|
|
s->pointer = data;
|
|
|
|
s->len++;
|
|
|
|
} else {
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_write(s, s->pointer & 0xf, data);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_autoinc(s);
|
2018-06-08 15:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static int pca955x_event(I2CSlave *i2c, enum i2c_event event)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(i2c);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
s->len = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_get_led(Object *obj, Visitor *v, const char *name,
|
2020-01-30 19:02:02 +03:00
|
|
|
void *opaque, Error **errp)
|
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(obj);
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(obj);
|
2020-01-30 19:02:02 +03:00
|
|
|
int led, rc, reg;
|
|
|
|
uint8_t state;
|
|
|
|
|
|
|
|
rc = sscanf(name, "led%2d", &led);
|
|
|
|
if (rc != 1) {
|
|
|
|
error_setg(errp, "%s: error reading %s", __func__, name);
|
|
|
|
return;
|
|
|
|
}
|
2020-06-23 10:27:18 +03:00
|
|
|
if (led < 0 || led > k->pin_count) {
|
2020-01-30 19:02:02 +03:00
|
|
|
error_setg(errp, "%s invalid led %s", __func__, name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Get the LSx register as the qom interface should expose the device
|
|
|
|
* state, not the modeled 'input line' behaviour which would come from
|
|
|
|
* reading the INPUTx reg
|
|
|
|
*/
|
|
|
|
reg = PCA9552_LS0 + led / 4;
|
2021-09-20 09:50:59 +03:00
|
|
|
state = (pca955x_read(s, reg) >> ((led % 4) * 2)) & 0x3;
|
2020-01-30 19:02:02 +03:00
|
|
|
visit_type_str(v, name, (char **)&led_state[state], errp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return an LED selector register value based on an existing one, with
|
|
|
|
* the appropriate 2-bit state value set for the given LED number (0-3).
|
|
|
|
*/
|
|
|
|
static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state)
|
|
|
|
{
|
|
|
|
return (oldval & (~(0x3 << (led_num << 1)))) |
|
|
|
|
((state & 0x3) << (led_num << 1));
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
|
2020-01-30 19:02:02 +03:00
|
|
|
void *opaque, Error **errp)
|
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(obj);
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(obj);
|
2020-01-30 19:02:02 +03:00
|
|
|
int led, rc, reg, val;
|
|
|
|
uint8_t state;
|
|
|
|
char *state_str;
|
|
|
|
|
error: Eliminate error_propagate() with Coccinelle, part 1
When all we do with an Error we receive into a local variable is
propagating to somewhere else, we can just as well receive it there
right away. Convert
if (!foo(..., &err)) {
...
error_propagate(errp, err);
...
return ...
}
to
if (!foo(..., errp)) {
...
...
return ...
}
where nothing else needs @err. Coccinelle script:
@rule1 forall@
identifier fun, err, errp, lbl;
expression list args, args2;
binary operator op;
constant c1, c2;
symbol false;
@@
if (
(
- fun(args, &err, args2)
+ fun(args, errp, args2)
|
- !fun(args, &err, args2)
+ !fun(args, errp, args2)
|
- fun(args, &err, args2) op c1
+ fun(args, errp, args2) op c1
)
)
{
... when != err
when != lbl:
when strict
- error_propagate(errp, err);
... when != err
(
return;
|
return c2;
|
return false;
)
}
@rule2 forall@
identifier fun, err, errp, lbl;
expression list args, args2;
expression var;
binary operator op;
constant c1, c2;
symbol false;
@@
- var = fun(args, &err, args2);
+ var = fun(args, errp, args2);
... when != err
if (
(
var
|
!var
|
var op c1
)
)
{
... when != err
when != lbl:
when strict
- error_propagate(errp, err);
... when != err
(
return;
|
return c2;
|
return false;
|
return var;
)
}
@depends on rule1 || rule2@
identifier err;
@@
- Error *err = NULL;
... when != err
Not exactly elegant, I'm afraid.
The "when != lbl:" is necessary to avoid transforming
if (fun(args, &err)) {
goto out
}
...
out:
error_propagate(errp, err);
even though other paths to label out still need the error_propagate().
For an actual example, see sclp_realize().
Without the "when strict", Coccinelle transforms vfio_msix_setup(),
incorrectly. I don't know what exactly "when strict" does, only that
it helps here.
The match of return is narrower than what I want, but I can't figure
out how to express "return where the operand doesn't use @err". For
an example where it's too narrow, see vfio_intx_enable().
Silently fails to convert hw/arm/armsse.c, because Coccinelle gets
confused by ARMSSE being used both as typedef and function-like macro
there. Converted manually.
Line breaks tidied up manually. One nested declaration of @local_err
deleted manually. Preexisting unwanted blank line dropped in
hw/riscv/sifive_e.c.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-Id: <20200707160613.848843-35-armbru@redhat.com>
2020-07-07 19:06:02 +03:00
|
|
|
if (!visit_type_str(v, name, &state_str, errp)) {
|
2020-01-30 19:02:02 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
rc = sscanf(name, "led%2d", &led);
|
|
|
|
if (rc != 1) {
|
|
|
|
error_setg(errp, "%s: error reading %s", __func__, name);
|
|
|
|
return;
|
|
|
|
}
|
2020-06-23 10:27:18 +03:00
|
|
|
if (led < 0 || led > k->pin_count) {
|
2020-01-30 19:02:02 +03:00
|
|
|
error_setg(errp, "%s invalid led %s", __func__, name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (state = 0; state < ARRAY_SIZE(led_state); state++) {
|
|
|
|
if (!strcmp(state_str, led_state[state])) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state >= ARRAY_SIZE(led_state)) {
|
|
|
|
error_setg(errp, "%s invalid led state %s", __func__, state_str);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg = PCA9552_LS0 + led / 4;
|
2020-06-23 10:27:17 +03:00
|
|
|
val = pca955x_read(s, reg);
|
2020-01-30 19:02:02 +03:00
|
|
|
val = pca955x_ledsel(val, led % 4, state);
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_write(s, reg, val);
|
2020-01-30 19:02:02 +03:00
|
|
|
}
|
|
|
|
|
2018-06-08 15:15:32 +03:00
|
|
|
static const VMStateDescription pca9552_vmstate = {
|
|
|
|
.name = "PCA9552",
|
|
|
|
.version_id = 0,
|
|
|
|
.minimum_version_id = 0,
|
2023-12-21 06:16:21 +03:00
|
|
|
.fields = (const VMStateField[]) {
|
2020-06-23 10:27:17 +03:00
|
|
|
VMSTATE_UINT8(len, PCA955xState),
|
|
|
|
VMSTATE_UINT8(pointer, PCA955xState),
|
|
|
|
VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
|
2024-02-05 10:40:15 +03:00
|
|
|
VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX),
|
2020-06-23 10:27:17 +03:00
|
|
|
VMSTATE_I2C_SLAVE(i2c, PCA955xState),
|
2018-06-08 15:15:32 +03:00
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void pca9552_reset(DeviceState *dev)
|
|
|
|
{
|
2020-06-23 10:27:17 +03:00
|
|
|
PCA955xState *s = PCA955X(dev);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
s->regs[PCA9552_PSC0] = 0xFF;
|
|
|
|
s->regs[PCA9552_PWM0] = 0x80;
|
|
|
|
s->regs[PCA9552_PSC1] = 0xFF;
|
|
|
|
s->regs[PCA9552_PWM1] = 0x80;
|
|
|
|
s->regs[PCA9552_LS0] = 0x55; /* all OFF */
|
|
|
|
s->regs[PCA9552_LS1] = 0x55;
|
|
|
|
s->regs[PCA9552_LS2] = 0x55;
|
|
|
|
s->regs[PCA9552_LS3] = 0x55;
|
|
|
|
|
2024-02-05 10:40:15 +03:00
|
|
|
memset(s->ext_state, PCA9552_PIN_HIZ, PCA955X_PIN_COUNT_MAX);
|
2020-06-23 10:27:17 +03:00
|
|
|
pca955x_update_pin_input(s);
|
2018-06-08 15:15:32 +03:00
|
|
|
|
|
|
|
s->pointer = 0xFF;
|
|
|
|
s->len = 0;
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_initfn(Object *obj)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(obj);
|
2020-01-30 19:02:02 +03:00
|
|
|
int led;
|
2018-06-08 15:15:32 +03:00
|
|
|
|
2020-06-23 10:27:18 +03:00
|
|
|
assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
|
|
|
|
for (led = 0; led < k->pin_count; led++) {
|
2020-01-30 19:02:02 +03:00
|
|
|
char *name;
|
|
|
|
|
|
|
|
name = g_strdup_printf("led%d", led);
|
2020-06-23 10:27:17 +03:00
|
|
|
object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
|
qom: Drop parameter @errp of object_property_add() & friends
The only way object_property_add() can fail is when a property with
the same name already exists. Since our property names are all
hardcoded, failure is a programming error, and the appropriate way to
handle it is passing &error_abort.
Same for its variants, except for object_property_add_child(), which
additionally fails when the child already has a parent. Parentage is
also under program control, so this is a programming error, too.
We have a bit over 500 callers. Almost half of them pass
&error_abort, slightly fewer ignore errors, one test case handles
errors, and the remaining few callers pass them to their own callers.
The previous few commits demonstrated once again that ignoring
programming errors is a bad idea.
Of the few ones that pass on errors, several violate the Error API.
The Error ** argument must be NULL, &error_abort, &error_fatal, or a
pointer to a variable containing NULL. Passing an argument of the
latter kind twice without clearing it in between is wrong: if the
first call sets an error, it no longer points to NULL for the second
call. ich9_pm_add_properties(), sparc32_ledma_realize(),
sparc32_dma_realize(), xilinx_axidma_realize(), xilinx_enet_realize()
are wrong that way.
When the one appropriate choice of argument is &error_abort, letting
users pick the argument is a bad idea.
Drop parameter @errp and assert the preconditions instead.
There's one exception to "duplicate property name is a programming
error": the way object_property_add() implements the magic (and
undocumented) "automatic arrayification". Don't drop @errp there.
Instead, rename object_property_add() to object_property_try_add(),
and add the obvious wrapper object_property_add().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-15-armbru@redhat.com>
[Two semantic rebase conflicts resolved]
2020-05-05 18:29:22 +03:00
|
|
|
NULL, NULL);
|
2020-01-30 19:02:02 +03:00
|
|
|
g_free(name);
|
|
|
|
}
|
2018-06-08 15:15:32 +03:00
|
|
|
}
|
|
|
|
|
2024-02-05 10:40:15 +03:00
|
|
|
static void pca955x_set_ext_state(PCA955xState *s, int pin, int level)
|
|
|
|
{
|
|
|
|
if (s->ext_state[pin] != level) {
|
|
|
|
uint16_t pins_status = pca955x_pins_get_status(s);
|
|
|
|
s->ext_state[pin] = level;
|
|
|
|
pca955x_update_pin_input(s);
|
|
|
|
pca955x_display_pins_status(s, pins_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pca955x_gpio_in_handler(void *opaque, int pin, int level)
|
|
|
|
{
|
|
|
|
|
|
|
|
PCA955xState *s = PCA955X(opaque);
|
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(s);
|
|
|
|
|
|
|
|
assert((pin >= 0) && (pin < k->pin_count));
|
|
|
|
pca955x_set_ext_state(s, pin, level);
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:19 +03:00
|
|
|
static void pca955x_realize(DeviceState *dev, Error **errp)
|
|
|
|
{
|
2020-06-23 10:27:23 +03:00
|
|
|
PCA955xClass *k = PCA955X_GET_CLASS(dev);
|
2020-06-23 10:27:19 +03:00
|
|
|
PCA955xState *s = PCA955X(dev);
|
|
|
|
|
|
|
|
if (!s->description) {
|
|
|
|
s->description = g_strdup("pca-unspecified");
|
|
|
|
}
|
2020-06-23 10:27:23 +03:00
|
|
|
|
2024-02-05 10:40:15 +03:00
|
|
|
qdev_init_gpio_out(dev, s->gpio_out, k->pin_count);
|
|
|
|
qdev_init_gpio_in(dev, pca955x_gpio_in_handler, k->pin_count);
|
2020-06-23 10:27:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static Property pca955x_properties[] = {
|
|
|
|
DEFINE_PROP_STRING("description", PCA955xState, description),
|
|
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
|
|
};
|
|
|
|
|
2020-06-23 10:27:18 +03:00
|
|
|
static void pca955x_class_init(ObjectClass *klass, void *data)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:19 +03:00
|
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
2018-06-08 15:15:32 +03:00
|
|
|
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
k->event = pca955x_event;
|
|
|
|
k->recv = pca955x_recv;
|
|
|
|
k->send = pca955x_send;
|
2020-06-23 10:27:19 +03:00
|
|
|
dc->realize = pca955x_realize;
|
|
|
|
device_class_set_props(dc, pca955x_properties);
|
2020-06-23 10:27:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static const TypeInfo pca955x_info = {
|
|
|
|
.name = TYPE_PCA955X,
|
|
|
|
.parent = TYPE_I2C_SLAVE,
|
|
|
|
.instance_init = pca955x_initfn,
|
|
|
|
.instance_size = sizeof(PCA955xState),
|
|
|
|
.class_init = pca955x_class_init,
|
hw/misc/pca9552: Add missing TypeInfo::class_size field
When adding the generic PCA955xClass in commit 736132e455, we
forgot to set the class_size field. Fill it now to avoid:
(gdb) run -machine mcimx6ul-evk -m 128M -display none -serial stdio -kernel ./OS.elf
Starting program: ../../qemu/qemu/arm-softmmu/qemu-system-arm -machine mcimx6ul-evk -m 128M -display none -serial stdio -kernel ./OS.elf
double free or corruption (!prev)
Thread 1 "qemu-system-arm" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
(gdb) where
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff75d8859 in __GI_abort () at abort.c:79
#2 0x00007ffff76433ee in __libc_message
(action=action@entry=do_abort, fmt=fmt@entry=0x7ffff776d285 "%s\n")
at ../sysdeps/posix/libc_fatal.c:155
#3 0x00007ffff764b47c in malloc_printerr
(str=str@entry=0x7ffff776f690 "double free or corruption (!prev)")
at malloc.c:5347
#4 0x00007ffff764d12c in _int_free
(av=0x7ffff779eb80 <main_arena>, p=0x5555567a3990, have_lock=<optimized out>) at malloc.c:4317
#5 0x0000555555c906c3 in type_initialize_interface
(ti=ti@entry=0x5555565b8f40, interface_type=0x555556597ad0, parent_type=0x55555662ca10) at qom/object.c:259
#6 0x0000555555c902da in type_initialize (ti=ti@entry=0x5555565b8f40)
at qom/object.c:323
#7 0x0000555555c90d20 in type_initialize (ti=0x5555565b8f40)
at qom/object.c:1028
$ valgrind --track-origins=yes qemu-system-arm -M mcimx6ul-evk -m 128M -display none -serial stdio -kernel ./OS.elf
==77479== Memcheck, a memory error detector
==77479== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==77479== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==77479== Command: qemu-system-arm -M mcimx6ul-evk -m 128M -display none -serial stdio -kernel ./OS.elf
==77479==
==77479== Invalid write of size 2
==77479== at 0x6D8322: pca9552_class_init (pca9552.c:424)
==77479== by 0x844D1F: type_initialize (object.c:1029)
==77479== by 0x844D1F: object_class_foreach_tramp (object.c:1016)
==77479== by 0x4AE1057: g_hash_table_foreach (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.6400.2)
==77479== by 0x8453A4: object_class_foreach (object.c:1038)
==77479== by 0x8453A4: object_class_get_list (object.c:1095)
==77479== by 0x556194: select_machine (vl.c:2416)
==77479== by 0x556194: qemu_init (vl.c:3828)
==77479== by 0x40AF9C: main (main.c:48)
==77479== Address 0x583f108 is 0 bytes after a block of size 200 alloc'd
==77479== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==77479== by 0x4AF8D30: g_malloc0 (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.6400.2)
==77479== by 0x844258: type_initialize.part.0 (object.c:306)
==77479== by 0x844D1F: type_initialize (object.c:1029)
==77479== by 0x844D1F: object_class_foreach_tramp (object.c:1016)
==77479== by 0x4AE1057: g_hash_table_foreach (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.6400.2)
==77479== by 0x8453A4: object_class_foreach (object.c:1038)
==77479== by 0x8453A4: object_class_get_list (object.c:1095)
==77479== by 0x556194: select_machine (vl.c:2416)
==77479== by 0x556194: qemu_init (vl.c:3828)
==77479== by 0x40AF9C: main (main.c:48)
Fixes: 736132e455 ("hw/misc/pca9552: Add generic PCA955xClass")
Reported-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Tested-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
Message-id: 20200629074704.23028-1-f4bug@amsat.org
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-06-29 10:47:04 +03:00
|
|
|
.class_size = sizeof(PCA955xClass),
|
2020-06-23 10:27:18 +03:00
|
|
|
.abstract = true,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void pca9552_class_init(ObjectClass *oc, void *data)
|
|
|
|
{
|
|
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
|
|
PCA955xClass *pc = PCA955X_CLASS(oc);
|
|
|
|
|
2018-06-08 15:15:32 +03:00
|
|
|
dc->reset = pca9552_reset;
|
|
|
|
dc->vmsd = &pca9552_vmstate;
|
2020-06-23 10:27:18 +03:00
|
|
|
pc->max_reg = PCA9552_LS3;
|
|
|
|
pc->pin_count = 16;
|
2018-06-08 15:15:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static const TypeInfo pca9552_info = {
|
|
|
|
.name = TYPE_PCA9552,
|
2020-06-23 10:27:18 +03:00
|
|
|
.parent = TYPE_PCA955X,
|
2018-06-08 15:15:32 +03:00
|
|
|
.class_init = pca9552_class_init,
|
|
|
|
};
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
static void pca955x_register_types(void)
|
2018-06-08 15:15:32 +03:00
|
|
|
{
|
2020-06-23 10:27:18 +03:00
|
|
|
type_register_static(&pca955x_info);
|
2018-06-08 15:15:32 +03:00
|
|
|
type_register_static(&pca9552_info);
|
|
|
|
}
|
|
|
|
|
2020-06-23 10:27:17 +03:00
|
|
|
type_init(pca955x_register_types)
|