Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
665 lines
18 KiB
C
665 lines
18 KiB
C
/*
|
|
* QEMU I3C bus interface.
|
|
*
|
|
* Copyright 2025 Google LLC
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qapi/error.h"
|
|
#include "trace.h"
|
|
#include "hw/i3c/i3c.h"
|
|
#include "hw/core/hotplug.h"
|
|
#include "hw/core/qdev-properties.h"
|
|
|
|
/*
|
|
* In test mode (enabled by ENTTM CCC) we're supposed to send a random PID
|
|
* during ENTDAA, so we'll just send "QEMU".
|
|
*/
|
|
#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL
|
|
|
|
static const Property i3c_props[] = {
|
|
DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, 0),
|
|
DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0),
|
|
DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0),
|
|
DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0),
|
|
};
|
|
|
|
static void i3c_realize(BusState *bus, Error **errp)
|
|
{
|
|
qbus_set_bus_hotplug_handler(bus);
|
|
}
|
|
|
|
static void i3c_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
BusClass *k = BUS_CLASS(klass);
|
|
k->realize = i3c_realize;
|
|
}
|
|
|
|
I3CBus *i3c_init_bus(DeviceState *parent, const char *name)
|
|
{
|
|
return i3c_init_bus_type(TYPE_I3C_BUS, parent, name);
|
|
}
|
|
|
|
I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
|
|
const char *name)
|
|
{
|
|
I3CBus *bus;
|
|
|
|
bus = I3C_BUS(qbus_new(type, parent, name));
|
|
QLIST_INIT(&bus->current_devs);
|
|
bus->broadcast = false;
|
|
bus->in_entdaa = false;
|
|
bus->in_ccc = false;
|
|
|
|
/* I2C init. */
|
|
g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c", name);
|
|
bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name);
|
|
|
|
return bus;
|
|
}
|
|
|
|
bool i3c_bus_busy(I3CBus *bus)
|
|
{
|
|
return !QLIST_EMPTY(&bus->current_devs);
|
|
}
|
|
|
|
static bool i3c_target_match(I3CTarget *candidate, uint8_t address,
|
|
bool is_recv, bool broadcast, bool in_entdaa)
|
|
{
|
|
/* Once a target has a dynamic address, it only responds to that. */
|
|
uint8_t targ_addr = candidate->address ? candidate->address :
|
|
candidate->static_address;
|
|
|
|
if (in_entdaa) {
|
|
if (address != I3C_BROADCAST) {
|
|
g_autofree char *path =
|
|
object_get_canonical_path(OBJECT(candidate));
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during "
|
|
"ENTDAA instead of a broadcast address\n",
|
|
path, address);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Targets should only ACK ENTDAA broadcasts if they have no dynamic
|
|
* address.
|
|
*/
|
|
return candidate->address == 0;
|
|
}
|
|
|
|
/* Return if our addresses match, or if it's a broadcast. */
|
|
return targ_addr == address || broadcast;
|
|
}
|
|
|
|
bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
|
|
enum I3CEvent event)
|
|
{
|
|
I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target);
|
|
bool matched = tc->target_match(target, address, event == I3C_START_RECV,
|
|
bus->broadcast, bus->in_entdaa);
|
|
|
|
if (matched) {
|
|
I3CNode *node = g_new(struct I3CNode, 1);
|
|
node->target = target;
|
|
QLIST_INSERT_HEAD(&bus->current_devs, node, next);
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event)
|
|
{
|
|
BusChild *child;
|
|
I3CNode *node, *next;
|
|
|
|
/* Clear out any devices from a previous (re-)START. */
|
|
QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
|
|
QLIST_REMOVE(node, next);
|
|
g_free(node);
|
|
}
|
|
|
|
QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
|
|
DeviceState *qdev = child->child;
|
|
I3CTarget *target = I3C_TARGET(qdev);
|
|
|
|
if (i3c_target_match_and_add(bus, target, address, event)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* No one on the bus could respond. */
|
|
return false;
|
|
}
|
|
|
|
/* Class-level event handling, since we do some CCCs at the class level. */
|
|
static int i3c_target_event(I3CTarget *t, enum I3CEvent event)
|
|
{
|
|
I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
|
|
trace_i3c_target_event(t->address, event);
|
|
|
|
if (event == I3C_STOP) {
|
|
t->curr_ccc = 0;
|
|
t->ccc_byte_offset = 0;
|
|
t->in_ccc = false;
|
|
}
|
|
return tc->event(t, event);
|
|
}
|
|
|
|
/*
|
|
* Sends a START or repeated START and the address for an I3C transaction.
|
|
*
|
|
* This function returns 0 if a device on the bus was able to respond to the
|
|
* address, and non-zero otherwise.
|
|
* A non-zero return represents a NACK.
|
|
*/
|
|
static int i3c_do_start_transfer(I3CBus *bus, uint8_t address,
|
|
enum I3CEvent event)
|
|
{
|
|
I3CTargetClass *tc;
|
|
I3CNode *node;
|
|
|
|
if (address == I3C_BROADCAST) {
|
|
bus->broadcast = true;
|
|
/* If we're not in ENTDAA, a broadcast is the start of a new CCC. */
|
|
if (!bus->in_entdaa) {
|
|
bus->in_ccc = false;
|
|
}
|
|
} else {
|
|
bus->broadcast = false;
|
|
}
|
|
|
|
/* No one responded to the address, NACK it. */
|
|
if (!i3c_scan_bus(bus, address, event)) {
|
|
return -1;
|
|
}
|
|
|
|
QLIST_FOREACH(node, &bus->current_devs, next) {
|
|
I3CTarget *t = node->target;
|
|
|
|
tc = I3C_TARGET_GET_CLASS(t);
|
|
if (tc->event) {
|
|
int rv = i3c_target_event(t, event);
|
|
if (rv && !bus->broadcast) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
|
|
{
|
|
trace_i3c_start_transfer(address, is_recv);
|
|
return i3c_do_start_transfer(bus, address, is_recv
|
|
? I3C_START_RECV
|
|
: I3C_START_SEND);
|
|
}
|
|
|
|
int i3c_start_recv(I3CBus *bus, uint8_t address)
|
|
{
|
|
trace_i3c_start_transfer(address, true);
|
|
return i3c_do_start_transfer(bus, address, I3C_START_RECV);
|
|
}
|
|
|
|
int i3c_start_send(I3CBus *bus, uint8_t address)
|
|
{
|
|
trace_i3c_start_transfer(address, false);
|
|
return i3c_do_start_transfer(bus, address, I3C_START_SEND);
|
|
}
|
|
|
|
void i3c_end_transfer(I3CBus *bus)
|
|
{
|
|
I3CTargetClass *tc;
|
|
I3CNode *node, *next;
|
|
|
|
trace_i3c_end_transfer();
|
|
|
|
/*
|
|
* If we're in ENTDAA, we need to notify all devices when ENTDAA is done.
|
|
* This is because everyone initially participates due to the broadcast,
|
|
* but gradually drops out as they get assigned addresses.
|
|
* Since the current_devs list only stores who's currently participating,
|
|
* and not everyone who previously participated, we send the STOP to all
|
|
* children.
|
|
*/
|
|
if (bus->in_entdaa) {
|
|
BusChild *child;
|
|
|
|
QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
|
|
DeviceState *qdev = child->child;
|
|
I3CTarget *t = I3C_TARGET(qdev);
|
|
tc = I3C_TARGET_GET_CLASS(t);
|
|
if (tc->event) {
|
|
i3c_target_event(t, I3C_STOP);
|
|
}
|
|
}
|
|
} else {
|
|
QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
|
|
I3CTarget *t = node->target;
|
|
tc = I3C_TARGET_GET_CLASS(t);
|
|
if (tc->event) {
|
|
i3c_target_event(t, I3C_STOP);
|
|
}
|
|
QLIST_REMOVE(node, next);
|
|
g_free(node);
|
|
}
|
|
}
|
|
bus->broadcast = false;
|
|
bus->in_entdaa = false;
|
|
bus->in_ccc = false;
|
|
}
|
|
|
|
/*
|
|
* Any CCCs that are universal across all I3C devices should be handled here.
|
|
* Once they're handled, we pass the CCC up to the I3C target to do anything
|
|
* else it may want with the bytes.
|
|
*/
|
|
static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data,
|
|
uint32_t num_to_send, uint32_t *num_sent)
|
|
{
|
|
I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
|
|
*num_sent = 0;
|
|
|
|
/* Is this the start of a new CCC? */
|
|
if (!t->in_ccc) {
|
|
t->curr_ccc = *data;
|
|
t->in_ccc = true;
|
|
*num_sent = 1;
|
|
trace_i3c_target_handle_ccc(t->address, t->curr_ccc);
|
|
}
|
|
|
|
switch (t->curr_ccc) {
|
|
case I3C_CCC_ENTDAA:
|
|
/*
|
|
* This is the last byte of ENTDAA, the controller is assigning us an
|
|
* address.
|
|
*/
|
|
if (t->ccc_byte_offset == 8) {
|
|
t->address = *data;
|
|
t->in_ccc = false;
|
|
t->curr_ccc = 0;
|
|
t->ccc_byte_offset = 0;
|
|
*num_sent = 1;
|
|
}
|
|
break;
|
|
case I3C_CCCD_SETDASA:
|
|
t->address = t->static_address;
|
|
break;
|
|
case I3C_CCC_SETAASA:
|
|
t->address = t->static_address;
|
|
break;
|
|
case I3C_CCC_RSTDAA:
|
|
t->address = 0;
|
|
break;
|
|
case I3C_CCCD_SETNEWDA:
|
|
/* If this isn't the CCC byte, it's our new address. */
|
|
if (*num_sent == 0) {
|
|
t->address = *data;
|
|
*num_sent = 1;
|
|
}
|
|
break;
|
|
case I3C_CCC_ENTTM:
|
|
/*
|
|
* If there are still more to look at, the next byte is the test mode
|
|
* byte.
|
|
*/
|
|
if (*num_sent != num_to_send) {
|
|
/* Enter test mode if the byte is non-zero. Otherwise exit. */
|
|
t->in_test_mode = !!data[*num_sent];
|
|
++*num_sent;
|
|
}
|
|
break;
|
|
/* Ignore other CCCs it's better to handle on a device-by-device basis. */
|
|
default:
|
|
break;
|
|
}
|
|
return tc->handle_ccc_write(t, data, num_to_send, num_sent);
|
|
}
|
|
|
|
int i3c_send_byte(I3CBus *bus, uint8_t data)
|
|
{
|
|
/*
|
|
* Ignored, the caller can determine how many were sent based on if this was
|
|
* ACKed/NACKed.
|
|
*/
|
|
uint32_t num_sent = 0;
|
|
return i3c_send(bus, &data, 1, &num_sent);
|
|
}
|
|
|
|
int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
|
|
uint32_t *num_sent)
|
|
{
|
|
I3CTargetClass *tc;
|
|
I3CTarget *t;
|
|
I3CNode *node;
|
|
int ret = 0;
|
|
|
|
/* If this message is a broadcast and no CCC has been found, grab it. */
|
|
if (bus->broadcast && !bus->in_ccc) {
|
|
bus->ccc = *data;
|
|
bus->in_ccc = true;
|
|
/*
|
|
* We need to keep track if we're currently in ENTDAA.
|
|
* On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA
|
|
* is only over on a STOP.
|
|
*/
|
|
if (bus->ccc == I3C_CCC_ENTDAA) {
|
|
bus->in_entdaa = true;
|
|
}
|
|
}
|
|
|
|
QLIST_FOREACH(node, &bus->current_devs, next) {
|
|
t = node->target;
|
|
tc = I3C_TARGET_GET_CLASS(t);
|
|
if (bus->in_ccc) {
|
|
if (!tc->handle_ccc_write) {
|
|
ret = -1;
|
|
continue;
|
|
}
|
|
ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent);
|
|
/* Targets should only NACK on a direct CCC. */
|
|
if (ret && !CCC_IS_DIRECT(bus->ccc)) {
|
|
ret = 0;
|
|
}
|
|
} else {
|
|
if (tc->send) {
|
|
ret = ret || tc->send(t, data, num_to_send, num_sent);
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_i3c_send(*num_sent, num_to_send, ret == 0);
|
|
|
|
return ret ? -1 : 0;
|
|
}
|
|
|
|
static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data,
|
|
uint32_t num_to_read, uint32_t *num_read)
|
|
{
|
|
I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
|
|
uint8_t read_count = 0;
|
|
uint64_t pid;
|
|
|
|
switch (t->curr_ccc) {
|
|
case I3C_CCC_ENTDAA:
|
|
if (t->in_test_mode) {
|
|
pid = TEST_MODE_PROVISIONED_ID;
|
|
} else {
|
|
pid = t->pid;
|
|
}
|
|
/* Return the 6-byte PID, followed by BCR then DCR. */
|
|
while (t->ccc_byte_offset < 6) {
|
|
if (read_count >= num_to_read) {
|
|
break;
|
|
}
|
|
data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff;
|
|
t->ccc_byte_offset++;
|
|
read_count++;
|
|
}
|
|
if (read_count < num_to_read) {
|
|
data[read_count] = t->bcr;
|
|
t->ccc_byte_offset++;
|
|
read_count++;
|
|
}
|
|
if (read_count < num_to_read) {
|
|
data[read_count] = t->dcr;
|
|
t->ccc_byte_offset++;
|
|
read_count++;
|
|
}
|
|
*num_read = read_count;
|
|
break;
|
|
case I3C_CCCD_GETPID:
|
|
while (t->ccc_byte_offset < 6) {
|
|
if (read_count >= num_to_read) {
|
|
break;
|
|
}
|
|
data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) & 0xff;
|
|
t->ccc_byte_offset++;
|
|
read_count++;
|
|
}
|
|
*num_read = read_count;
|
|
break;
|
|
case I3C_CCCD_GETBCR:
|
|
*data = t->bcr;
|
|
*num_read = 1;
|
|
break;
|
|
case I3C_CCCD_GETDCR:
|
|
*data = t->dcr;
|
|
*num_read = 1;
|
|
break;
|
|
default:
|
|
/* Unhandled on the I3CTarget class level. */
|
|
break;
|
|
}
|
|
|
|
return tc->handle_ccc_read(t, data, num_to_read, num_read);
|
|
}
|
|
|
|
int i3c_recv_byte(I3CBus *bus, uint8_t *data)
|
|
{
|
|
/*
|
|
* Ignored, the caller can determine how many bytes were read based on if
|
|
* this is ACKed/NACKed.
|
|
*/
|
|
uint32_t num_read;
|
|
return i3c_recv(bus, data, 1, &num_read);
|
|
}
|
|
|
|
int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
|
|
uint32_t *num_read)
|
|
{
|
|
int ret = 0;
|
|
I3CTargetClass *tc;
|
|
I3CTarget *t;
|
|
|
|
*data = 0xff;
|
|
if (!QLIST_EMPTY(&bus->current_devs)) {
|
|
tc = I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target);
|
|
t = QLIST_FIRST(&bus->current_devs)->target;
|
|
if (bus->in_ccc) {
|
|
if (!tc->handle_ccc_read) {
|
|
return -1;
|
|
}
|
|
ret = i3c_target_handle_ccc_read(t, data, num_to_read, num_read);
|
|
} else {
|
|
if (tc->recv) {
|
|
/*
|
|
* Targets cannot NACK on a direct transfer, so the data
|
|
* is returned directly.
|
|
*/
|
|
*num_read = tc->recv(t, data, num_to_read);
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_i3c_recv(*num_read, num_to_read, ret == 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void i3c_nack(I3CBus *bus)
|
|
{
|
|
I3CTargetClass *tc;
|
|
I3CNode *node;
|
|
|
|
if (QLIST_EMPTY(&bus->current_devs)) {
|
|
return;
|
|
}
|
|
|
|
QLIST_FOREACH(node, &bus->current_devs, next) {
|
|
tc = I3C_TARGET_GET_CLASS(node->target);
|
|
if (tc->event) {
|
|
i3c_target_event(node->target, I3C_NACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv)
|
|
{
|
|
I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
|
|
I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
|
|
trace_i3c_target_send_ibi(addr, is_recv);
|
|
return bc->ibi_handle(bus, addr, is_recv);
|
|
}
|
|
|
|
int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data)
|
|
{
|
|
I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
|
|
I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
|
|
trace_i3c_target_send_ibi_bytes(data);
|
|
return bc->ibi_recv(bus, data);
|
|
}
|
|
|
|
int i3c_target_ibi_finish(I3CTarget *t, uint8_t data)
|
|
{
|
|
I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
|
|
I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
|
|
trace_i3c_target_ibi_finish();
|
|
return bc->ibi_finish(bus);
|
|
}
|
|
|
|
static bool i3c_addr_is_rsvd(uint8_t addr)
|
|
{
|
|
static const bool is_rsvd[256] = {
|
|
[0x00] = true,
|
|
[0x01] = true,
|
|
[0x02] = true,
|
|
[0x3e] = true,
|
|
[0x5e] = true,
|
|
[0x6e] = true,
|
|
[0x76] = true,
|
|
[0x7a] = true,
|
|
[0x7c] = true,
|
|
[0x7e] = true,
|
|
[0x7f] = true,
|
|
};
|
|
|
|
return is_rsvd[addr];
|
|
}
|
|
|
|
I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
|
|
uint8_t bcr, uint64_t pid)
|
|
{
|
|
DeviceState *dev;
|
|
|
|
dev = qdev_new(name);
|
|
qdev_prop_set_uint8(dev, "static-address", addr);
|
|
qdev_prop_set_uint8(dev, "dcr", dcr);
|
|
qdev_prop_set_uint8(dev, "bcr", bcr);
|
|
qdev_prop_set_uint64(dev, "pid", pid);
|
|
|
|
if (i3c_addr_is_rsvd(addr)) {
|
|
g_autofree char *path = object_get_canonical_path(OBJECT(dev));
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved "
|
|
"address 0x%.2x\n", path, addr);
|
|
}
|
|
return I3C_TARGET(dev);
|
|
}
|
|
|
|
bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp)
|
|
{
|
|
return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp);
|
|
}
|
|
|
|
I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t addr,
|
|
uint8_t dcr, uint8_t bcr, uint64_t pid)
|
|
{
|
|
I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid);
|
|
dev->address = 0;
|
|
i3c_target_realize_and_unref(dev, bus, &error_abort);
|
|
|
|
return dev;
|
|
}
|
|
|
|
/* Legacy I2C functions. */
|
|
void legacy_i2c_nack(I3CBus *bus)
|
|
{
|
|
trace_legacy_i2c_nack();
|
|
i2c_nack(bus->i2c_bus);
|
|
}
|
|
|
|
uint8_t legacy_i2c_recv(I3CBus *bus)
|
|
{
|
|
uint8_t byte = i2c_recv(bus->i2c_bus);
|
|
trace_legacy_i2c_recv(byte);
|
|
return byte;
|
|
}
|
|
|
|
int legacy_i2c_send(I3CBus *bus, uint8_t data)
|
|
{
|
|
trace_legacy_i2c_send(data);
|
|
return i2c_send(bus->i2c_bus, data);
|
|
}
|
|
|
|
int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
|
|
{
|
|
trace_legacy_i2c_start_transfer(address, is_recv);
|
|
return i2c_start_transfer(bus->i2c_bus, address, is_recv);
|
|
}
|
|
|
|
int legacy_i2c_start_recv(I3CBus *bus, uint8_t address)
|
|
{
|
|
trace_legacy_i2c_start_transfer(address, true);
|
|
return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/true);
|
|
}
|
|
|
|
int legacy_i2c_start_send(I3CBus *bus, uint8_t address)
|
|
{
|
|
trace_legacy_i2c_start_transfer(address, false);
|
|
return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/false);
|
|
}
|
|
|
|
void legacy_i2c_end_transfer(I3CBus *bus)
|
|
{
|
|
trace_legacy_i2c_end_transfer();
|
|
i2c_end_transfer(bus->i2c_bus);
|
|
}
|
|
|
|
I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
|
|
uint8_t addr)
|
|
{
|
|
I2CSlave *dev = i2c_slave_new(name, addr);
|
|
|
|
i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort);
|
|
return dev;
|
|
}
|
|
|
|
static void i3c_target_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *k = DEVICE_CLASS(klass);
|
|
I3CTargetClass *sc = I3C_TARGET_CLASS(klass);
|
|
set_bit(DEVICE_CATEGORY_MISC, k->categories);
|
|
k->bus_type = TYPE_I3C_BUS;
|
|
device_class_set_props(k, i3c_props);
|
|
sc->target_match = i3c_target_match;
|
|
}
|
|
|
|
static const TypeInfo i3c_types[] = {
|
|
{
|
|
.name = TYPE_I3C_BUS,
|
|
.parent = TYPE_BUS,
|
|
.instance_size = sizeof(I3CBus),
|
|
.class_size = sizeof(I3CBusClass),
|
|
.class_init = i3c_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_HOTPLUG_HANDLER },
|
|
{ }
|
|
}
|
|
},
|
|
{
|
|
.name = TYPE_I3C_TARGET,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_size = sizeof(I3CTarget),
|
|
.abstract = true,
|
|
.class_size = sizeof(I3CTargetClass),
|
|
.class_init = i3c_target_class_init,
|
|
},
|
|
};
|
|
|
|
DEFINE_TYPES(i3c_types)
|