| /* |
| * 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); |
| } |