271 lines
5.8 KiB
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);
|
|
}
|