1
0
Fork 0
alistair23-linux/drivers/staging/greybus/legacy.c

271 lines
5.8 KiB
C

/*
* Greybus legacy-protocol driver
*
* Copyright 2015 Google Inc.
* Copyright 2015 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#include "greybus.h"
#include "legacy.h"
#include "protocol.h"
struct legacy_connection {
struct gb_connection *connection;
bool initialized;
struct gb_protocol *protocol;
};
struct legacy_data {
size_t num_cports;
struct legacy_connection *connections;
};
static int legacy_connection_get_version(struct gb_connection *connection)
{
int ret;
ret = gb_protocol_get_version(connection);
if (ret) {
dev_err(&connection->hd->dev,
"%s: failed to get protocol version: %d\n",
connection->name, ret);
return ret;
}
return 0;
}
static int legacy_request_handler(struct gb_operation *operation)
{
struct gb_protocol *protocol = operation->connection->protocol;
return protocol->request_recv(operation->type, operation);
}
static int legacy_connection_init(struct legacy_connection *lc)
{
struct gb_connection *connection = lc->connection;
int ret;
dev_dbg(&connection->bundle->dev, "%s - %s\n", __func__,
connection->name);
ret = gb_connection_enable(connection);
if (ret)
return ret;
ret = legacy_connection_get_version(connection);
if (ret)
goto err_disable;
ret = connection->protocol->connection_init(connection);
if (ret)
goto err_disable;
lc->initialized = true;
return 0;
err_disable:
gb_connection_disable(connection);
return ret;
}
static void legacy_connection_exit(struct legacy_connection *lc)
{
struct gb_connection *connection = lc->connection;
if (!lc->initialized)
return;
gb_connection_disable(connection);
connection->protocol->connection_exit(connection);
lc->initialized = false;
}
static int legacy_connection_create(struct legacy_connection *lc,
struct gb_bundle *bundle,
struct greybus_descriptor_cport *desc)
{
struct gb_connection *connection;
struct gb_protocol *protocol;
gb_request_handler_t handler;
u8 major, minor;
int ret;
/*
* The legacy protocols have always been looked up using a hard-coded
* version of 0.1, despite (or perhaps rather, due to) the fact that
* module version negotiation could not take place until after the
* protocol was bound.
*/
major = 0;
minor = 1;
protocol = gb_protocol_get(desc->protocol_id, major, minor);
if (!protocol) {
dev_err(&bundle->dev,
"protocol 0x%02x version %u.%u not found\n",
desc->protocol_id, major, minor);
return -EPROTONOSUPPORT;
}
if (protocol->request_recv)
handler = legacy_request_handler;
else
handler = NULL;
connection = gb_connection_create(bundle, le16_to_cpu(desc->id),
handler);
if (IS_ERR(connection)) {
ret = PTR_ERR(connection);
goto err_protocol_put;
}
/*
* NOTE: We need to keep a pointer to the protocol in the actual
* connection structure for now.
*/
connection->protocol = protocol;
lc->connection = connection;
lc->protocol = protocol;
return 0;
err_protocol_put:
gb_protocol_put(protocol);
return ret;
}
static void legacy_connection_destroy(struct legacy_connection *lc)
{
if (!lc->connection)
return;
lc->connection->protocol = NULL;
gb_connection_destroy(lc->connection);
gb_protocol_put(lc->protocol);
}
static int legacy_probe(struct gb_bundle *bundle,
const struct greybus_bundle_id *id)
{
struct greybus_descriptor_cport *cport_desc;
struct legacy_data *data;
struct legacy_connection *lc;
int i;
int ret;
dev_dbg(&bundle->dev,
"%s - bundle class = 0x%02x, num_cports = %zu\n",
__func__, bundle->class, bundle->num_cports);
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->num_cports = bundle->num_cports;
data->connections = kcalloc(data->num_cports,
sizeof(*data->connections),
GFP_KERNEL);
if (!data->connections) {
ret = -ENOMEM;
goto err_free_data;
}
for (i = 0; i < data->num_cports; ++i) {
cport_desc = &bundle->cport_desc[i];
lc = &data->connections[i];
ret = legacy_connection_create(lc, bundle, cport_desc);
if (ret)
goto err_connections_destroy;
}
greybus_set_drvdata(bundle, data);
for (i = 0; i < data->num_cports; ++i) {
lc = &data->connections[i];
ret = legacy_connection_init(lc);
if (ret)
goto err_connections_disable;
}
return 0;
err_connections_disable:
for (--i; i >= 0; --i)
legacy_connection_exit(&data->connections[i]);
err_connections_destroy:
for (i = 0; i < data->num_cports; ++i)
legacy_connection_destroy(&data->connections[i]);
kfree(data->connections);
err_free_data:
kfree(data);
return ret;
}
static void legacy_disconnect(struct gb_bundle *bundle)
{
struct legacy_data *data = greybus_get_drvdata(bundle);
int i;
dev_dbg(&bundle->dev, "%s - bundle class = 0x%02x\n", __func__,
bundle->class);
for (i = 0; i < data->num_cports; ++i) {
legacy_connection_exit(&data->connections[i]);
legacy_connection_destroy(&data->connections[i]);
}
kfree(data->connections);
kfree(data);
}
static const struct greybus_bundle_id legacy_id_table[] = {
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_GPIO) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_I2C) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_UART) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_USB) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_SDIO) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_POWER_SUPPLY) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_PWM) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_SPI) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_CAMERA) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LIGHTS) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LOOPBACK) },
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_RAW) },
{ }
};
MODULE_DEVICE_TABLE(greybus, legacy_id_table);
static struct greybus_driver legacy_driver = {
.name = "legacy",
.probe = legacy_probe,
.disconnect = legacy_disconnect,
.id_table = legacy_id_table,
};
int gb_legacy_init(void)
{
return greybus_register(&legacy_driver);
}
void gb_legacy_exit(void)
{
greybus_deregister(&legacy_driver);
}