hw/i2c: support multiple masters
Allow slaves to master the bus by registering a bottom halve. If the bus is busy, the bottom half is queued up. When a slave has succesfully mastered the bus, the bottom half is scheduled. Signed-off-by: Klaus Jensen <k.jensen@samsung.com> [ clg : - fixed typos in commit log ] Message-Id: <20220601210831.67259-4-its@irrelevant.dk> Signed-off-by: Cédric Le Goater <clg@kaod.org> Message-Id: <20220630045133.32251-5-me@pjd.dev> Signed-off-by: Cédric Le Goater <clg@kaod.org>
This commit is contained in:
parent
0c0f1bee6a
commit
37fa5ca426
@ -13,6 +13,7 @@
|
|||||||
#include "migration/vmstate.h"
|
#include "migration/vmstate.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
|
#include "qemu/main-loop.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
#define I2C_BROADCAST 0x00
|
#define I2C_BROADCAST 0x00
|
||||||
@ -62,6 +63,7 @@ I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
|
|||||||
|
|
||||||
bus = I2C_BUS(qbus_new(TYPE_I2C_BUS, parent, name));
|
bus = I2C_BUS(qbus_new(TYPE_I2C_BUS, parent, name));
|
||||||
QLIST_INIT(&bus->current_devs);
|
QLIST_INIT(&bus->current_devs);
|
||||||
|
QSIMPLEQ_INIT(&bus->pending_masters);
|
||||||
vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &vmstate_i2c_bus, bus);
|
vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &vmstate_i2c_bus, bus);
|
||||||
return bus;
|
return bus;
|
||||||
}
|
}
|
||||||
@ -74,7 +76,7 @@ void i2c_slave_set_address(I2CSlave *dev, uint8_t address)
|
|||||||
/* Return nonzero if bus is busy. */
|
/* Return nonzero if bus is busy. */
|
||||||
int i2c_bus_busy(I2CBus *bus)
|
int i2c_bus_busy(I2CBus *bus)
|
||||||
{
|
{
|
||||||
return !QLIST_EMPTY(&bus->current_devs);
|
return !QLIST_EMPTY(&bus->current_devs) || bus->bh;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
|
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
|
||||||
@ -180,6 +182,26 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv)
|
|||||||
: I2C_START_SEND);
|
: I2C_START_SEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void i2c_bus_master(I2CBus *bus, QEMUBH *bh)
|
||||||
|
{
|
||||||
|
if (i2c_bus_busy(bus)) {
|
||||||
|
I2CPendingMaster *node = g_new(struct I2CPendingMaster, 1);
|
||||||
|
node->bh = bh;
|
||||||
|
|
||||||
|
QSIMPLEQ_INSERT_TAIL(&bus->pending_masters, node, entry);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus->bh = bh;
|
||||||
|
qemu_bh_schedule(bus->bh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2c_bus_release(I2CBus *bus)
|
||||||
|
{
|
||||||
|
bus->bh = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int i2c_start_recv(I2CBus *bus, uint8_t address)
|
int i2c_start_recv(I2CBus *bus, uint8_t address)
|
||||||
{
|
{
|
||||||
return i2c_do_start_transfer(bus, address, I2C_START_RECV);
|
return i2c_do_start_transfer(bus, address, I2C_START_RECV);
|
||||||
@ -206,6 +228,16 @@ void i2c_end_transfer(I2CBus *bus)
|
|||||||
g_free(node);
|
g_free(node);
|
||||||
}
|
}
|
||||||
bus->broadcast = false;
|
bus->broadcast = false;
|
||||||
|
|
||||||
|
if (!QSIMPLEQ_EMPTY(&bus->pending_masters)) {
|
||||||
|
I2CPendingMaster *node = QSIMPLEQ_FIRST(&bus->pending_masters);
|
||||||
|
bus->bh = node->bh;
|
||||||
|
|
||||||
|
QSIMPLEQ_REMOVE_HEAD(&bus->pending_masters, entry);
|
||||||
|
g_free(node);
|
||||||
|
|
||||||
|
qemu_bh_schedule(bus->bh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int i2c_send(I2CBus *bus, uint8_t data)
|
int i2c_send(I2CBus *bus, uint8_t data)
|
||||||
|
@ -69,13 +69,25 @@ struct I2CNode {
|
|||||||
QLIST_ENTRY(I2CNode) next;
|
QLIST_ENTRY(I2CNode) next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct I2CPendingMaster I2CPendingMaster;
|
||||||
|
|
||||||
|
struct I2CPendingMaster {
|
||||||
|
QEMUBH *bh;
|
||||||
|
QSIMPLEQ_ENTRY(I2CPendingMaster) entry;
|
||||||
|
};
|
||||||
|
|
||||||
typedef QLIST_HEAD(I2CNodeList, I2CNode) I2CNodeList;
|
typedef QLIST_HEAD(I2CNodeList, I2CNode) I2CNodeList;
|
||||||
|
typedef QSIMPLEQ_HEAD(I2CPendingMasters, I2CPendingMaster) I2CPendingMasters;
|
||||||
|
|
||||||
struct I2CBus {
|
struct I2CBus {
|
||||||
BusState qbus;
|
BusState qbus;
|
||||||
I2CNodeList current_devs;
|
I2CNodeList current_devs;
|
||||||
|
I2CPendingMasters pending_masters;
|
||||||
uint8_t saved_address;
|
uint8_t saved_address;
|
||||||
bool broadcast;
|
bool broadcast;
|
||||||
|
|
||||||
|
/* Set from slave currently mastering the bus. */
|
||||||
|
QEMUBH *bh;
|
||||||
};
|
};
|
||||||
|
|
||||||
I2CBus *i2c_init_bus(DeviceState *parent, const char *name);
|
I2CBus *i2c_init_bus(DeviceState *parent, const char *name);
|
||||||
@ -117,6 +129,8 @@ int i2c_start_send(I2CBus *bus, uint8_t address);
|
|||||||
|
|
||||||
void i2c_end_transfer(I2CBus *bus);
|
void i2c_end_transfer(I2CBus *bus);
|
||||||
void i2c_nack(I2CBus *bus);
|
void i2c_nack(I2CBus *bus);
|
||||||
|
void i2c_bus_master(I2CBus *bus, QEMUBH *bh);
|
||||||
|
void i2c_bus_release(I2CBus *bus);
|
||||||
int i2c_send(I2CBus *bus, uint8_t data);
|
int i2c_send(I2CBus *bus, uint8_t data);
|
||||||
uint8_t i2c_recv(I2CBus *bus);
|
uint8_t i2c_recv(I2CBus *bus);
|
||||||
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
|
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
|
||||||
|
Loading…
Reference in New Issue
Block a user