summaryrefslogtreecommitdiff
path: root/drivers/hsi/clients/cfhsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hsi/clients/cfhsi.c')
-rw-r--r--drivers/hsi/clients/cfhsi.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/drivers/hsi/clients/cfhsi.c b/drivers/hsi/clients/cfhsi.c
new file mode 100644
index 00000000000..cf7ce0cb1cb
--- /dev/null
+++ b/drivers/hsi/clients/cfhsi.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Daniel Martensson <Daniel.Martensson@stericsson.com>
+ * License terms: GNU General Public License (GPL) version 2.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+
+#include <net/caif/caif_hsi.h>
+
+#include <linux/hsi/hsi.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Daniel Martensson<daniel.martensson@stericsson.com>");
+MODULE_DESCRIPTION("CAIF HSI V3 glue");
+
+#define NR_OF_CAIF_HSI_CHANNELS 2
+
+struct cfhsi_v3 {
+ struct list_head list;
+ struct cfhsi_dev dev;
+ struct platform_device pdev;
+ struct hsi_msg *tx_msg;
+ struct hsi_msg *rx_msg;
+};
+
+/* TODO: Lists are not protected with regards to device removal. */
+static LIST_HEAD(cfhsi_dev_list);
+
+static struct hsi_client *cfhsi_client;
+
+static int cfhsi_tx(u8 *ptr, int len, struct cfhsi_dev *dev)
+{
+ int res;
+ struct cfhsi_v3 *cfhsi = NULL;
+
+ /* Check length and alignment. */
+ BUG_ON(((int)ptr)%4);
+ BUG_ON(len%4);
+
+ cfhsi = container_of(dev, struct cfhsi_v3, dev);
+
+ sg_init_one(cfhsi->tx_msg->sgt.sgl, (const void *)ptr,
+ (unsigned int)len);
+
+ /* Write on HSI device. */
+ res = hsi_async_write(cfhsi_client, cfhsi->tx_msg);
+
+ return res;
+}
+
+static int cfhsi_rx(u8 *ptr, int len, struct cfhsi_dev *dev)
+{
+ int res;
+ struct cfhsi_v3 *cfhsi = NULL;
+
+ /* Check length and alignment. */
+ BUG_ON(((int)ptr)%4);
+ BUG_ON(len%4);
+
+ cfhsi = container_of(dev, struct cfhsi_v3, dev);
+
+ sg_init_one(cfhsi->rx_msg->sgt.sgl, (const void *)ptr,
+ (unsigned int)len);
+
+ /* Read from HSI device. */
+ res = hsi_async_read(cfhsi_client, cfhsi->rx_msg);
+
+ return res;
+}
+
+void cfhsi_v3_release(struct device *dev)
+{
+ pr_warning("%s:%d cfhsi_v3_release called\n", __FILE__, __LINE__);
+}
+
+static inline void cfhsi_v3_destructor(struct hsi_msg *msg)
+{
+ pr_warning("%s:%d cfhsi_v3_destructor called\n", __FILE__, __LINE__);
+}
+
+static inline void cfhsi_v3_read_cb(struct hsi_msg *msg)
+{
+ struct cfhsi_v3 *cfhsi = (struct cfhsi_v3 *)msg->context;
+
+ /* TODO: Error checking. */
+ BUG_ON(!cfhsi->dev.drv);
+ BUG_ON(!cfhsi->dev.drv->rx_done_cb);
+
+ cfhsi->dev.drv->rx_done_cb(cfhsi->dev.drv);
+}
+
+static inline void cfhsi_v3_write_cb(struct hsi_msg *msg)
+{
+ struct cfhsi_v3 *cfhsi = (struct cfhsi_v3 *)msg->context;
+
+ /* TODO: Error checking. */
+ BUG_ON(!cfhsi->dev.drv);
+ BUG_ON(!cfhsi->dev.drv->tx_done_cb);
+
+ cfhsi->dev.drv->tx_done_cb(cfhsi->dev.drv);
+}
+
+static int hsi_proto_probe(struct device *dev)
+{
+ int res;
+ int i;
+ struct cfhsi_v3 *cfhsi = NULL;
+
+ if (cfhsi_client)
+ return -ENODEV; /* TODO: Not correct return. */
+
+ cfhsi_client = to_hsi_client(dev);
+
+ res = hsi_claim_port(cfhsi_client, 0);
+ if (res) {
+ pr_warning("hsi_proto_probe: hsi_claim_port:%d.\n", res);
+ goto err_hsi_claim;
+ }
+
+ /* Right now we don't care about AC_WAKE (No power management). */
+ cfhsi_client->hsi_start_rx = NULL;
+ cfhsi_client->hsi_stop_rx = NULL;
+
+ /* CAIF HSI TX configuration. */
+ cfhsi_client->tx_cfg.mode = HSI_MODE_STREAM;
+ cfhsi_client->tx_cfg.flow = HSI_FLOW_SYNC;
+ cfhsi_client->tx_cfg.channels = NR_OF_CAIF_HSI_CHANNELS;
+ cfhsi_client->tx_cfg.speed = 100000; /* TODO: What speed should be used. */
+ cfhsi_client->tx_cfg.arb_mode = HSI_ARB_RR;
+
+ /* CAIF HSI RX configuration. */
+ cfhsi_client->rx_cfg.mode = HSI_MODE_STREAM;
+ cfhsi_client->rx_cfg.flow = HSI_FLOW_SYNC;
+ cfhsi_client->rx_cfg.channels = NR_OF_CAIF_HSI_CHANNELS;
+ cfhsi_client->rx_cfg.speed = 200000; /* TODO: What speed should be used. */
+ cfhsi_client->rx_cfg.arb_mode = HSI_ARB_RR;
+
+ res = hsi_setup(cfhsi_client);
+ if (res) {
+ pr_warning("hsi_proto_probe: hsi_setup:%d.\n", res);
+ goto err_hsi_setup;
+ }
+
+ /* Make sure that AC_WAKE is high (No power management). */
+ res = hsi_start_tx(cfhsi_client);
+ if (res) {
+ pr_warning("hsi_proto_probe: hsi_start_tx:%d.\n", res);
+ goto err_hsi_start_tx;
+ }
+
+ /* Connect channels to CAIF HSI devices. */
+ for (i = 0; i < NR_OF_CAIF_HSI_CHANNELS; i++) {
+ cfhsi = kzalloc(sizeof(struct cfhsi_v3), GFP_KERNEL);
+ if (!cfhsi) {
+ res = -ENOMEM;
+ /* TODO: Error handling. */
+ }
+
+ /* Assign HSI client to this CAIF HSI device. */
+ cfhsi->dev.cfhsi_tx = cfhsi_tx;
+ cfhsi->dev.cfhsi_rx = cfhsi_rx;
+
+ /* Allocate HSI messages. */
+ cfhsi->tx_msg = hsi_alloc_msg(1, GFP_KERNEL);
+ cfhsi->rx_msg = hsi_alloc_msg(1, GFP_KERNEL);
+ if (!cfhsi->tx_msg || !cfhsi->rx_msg) {
+ res = -ENOMEM;
+ /* TODO: Error handling. */
+ }
+
+ /* Set up TX message. */
+ cfhsi->tx_msg->cl = cfhsi_client;
+ cfhsi->tx_msg->context = (void *)cfhsi;
+ cfhsi->tx_msg->complete = cfhsi_v3_write_cb;
+ cfhsi->tx_msg->destructor = cfhsi_v3_destructor;
+ cfhsi->tx_msg->channel = i;
+ cfhsi->tx_msg->ttype = HSI_MSG_WRITE;
+ cfhsi->tx_msg->break_frame = 0; /* No break frame. */
+
+ /* Set up RX message. */
+ cfhsi->rx_msg->cl = cfhsi_client;
+ cfhsi->rx_msg->context = (void *)cfhsi;
+ cfhsi->rx_msg->complete = cfhsi_v3_read_cb;
+ cfhsi->rx_msg->destructor = cfhsi_v3_destructor;
+ cfhsi->rx_msg->channel = i;
+ cfhsi->rx_msg->ttype = HSI_MSG_READ;
+ cfhsi->rx_msg->break_frame = 0; /* No break frame. */
+
+ /* Initialize CAIF HSI platform device. */
+ cfhsi->pdev.name = "cfhsi";
+ cfhsi->pdev.dev.platform_data = &cfhsi->dev;
+ cfhsi->pdev.dev.release = cfhsi_v3_release;
+ /* Use channel number as id. */
+ cfhsi->pdev.id = i;
+ /* Register platform device. */
+ res = platform_device_register(&cfhsi->pdev);
+ if (res) {
+ pr_warning("hsi_proto_probe: plat_dev_reg:%d.\n", res);
+ res = -ENODEV;
+ /* TODO: Error handling. */
+ }
+
+ /* Add HSI device to device list. */
+ list_add_tail(&cfhsi->list, &cfhsi_dev_list);
+ }
+
+ return res;
+
+ err_hsi_start_tx:
+ err_hsi_setup:
+ hsi_release_port(cfhsi_client);
+ err_hsi_claim:
+ cfhsi_client = NULL;
+
+ return res;
+}
+
+static int hsi_proto_remove(struct device *dev)
+{
+ struct cfhsi_v3 *cfhsi = NULL;
+ struct list_head *list_node;
+ struct list_head *n;
+
+ if (!cfhsi_client)
+ return -ENODEV;
+
+ list_for_each_safe(list_node, n, &cfhsi_dev_list) {
+ cfhsi = list_entry(list_node, struct cfhsi_v3, list);
+ /* Remove from list. */
+ list_del(list_node);
+ /* Our HSI device is gone, unregister CAIF HSI device. */
+ platform_device_del(&cfhsi->pdev);
+ hsi_free_msg(cfhsi->tx_msg);
+ hsi_free_msg(cfhsi->rx_msg);
+ /* Free memory. */
+ kfree(cfhsi);
+ }
+
+ hsi_stop_tx(cfhsi_client);
+ hsi_release_port(cfhsi_client);
+
+ cfhsi_client = NULL;
+
+ return 0;
+}
+
+static int hsi_proto_suspend(struct device *dev, pm_message_t mesg)
+{
+ /* Not handled. */
+ pr_info("hsi_proto_suspend.\n");
+
+ return 0;
+}
+
+static int hsi_proto_resume(struct device *dev)
+{
+ /* Not handled. */
+ pr_info("hsi_proto_resume.\n");
+
+ return 0;
+}
+
+static struct hsi_client_driver cfhsi_v3_driver = {
+ .driver = {
+ .name = "cfhsi_v3_driver",
+ .owner = THIS_MODULE,
+ .probe = hsi_proto_probe,
+ .remove = __devexit_p(hsi_proto_remove),
+ .suspend = hsi_proto_suspend,
+ .resume = hsi_proto_resume,
+ },
+};
+
+static int __init cfhsi_v3_init(void)
+{
+ int res;
+
+ /* Register protocol driver for HSI interface. */
+ res = hsi_register_client_driver(&cfhsi_v3_driver);
+ if (res)
+ pr_warning("Failed to register CAIF HSI V3 driver.\n");
+
+ return res;
+}
+
+static void __exit cfhsi_v3_exit(void)
+{
+ struct cfhsi_v3 *cfhsi = NULL;
+ struct list_head *list_node;
+ struct list_head *n;
+
+ /* Unregister driver. */
+ hsi_unregister_client_driver(&cfhsi_v3_driver);
+
+ if (!cfhsi_client)
+ return;
+
+ list_for_each_safe(list_node, n, &cfhsi_dev_list) {
+ cfhsi = list_entry(list_node, struct cfhsi_v3, list);
+ platform_device_del(&cfhsi->pdev);
+ hsi_free_msg(cfhsi->tx_msg);
+ hsi_free_msg(cfhsi->rx_msg);
+ kfree(cfhsi);
+ }
+
+ hsi_stop_tx(cfhsi_client);
+ hsi_release_port(cfhsi_client);
+
+ cfhsi_client = NULL;
+}
+
+module_init(cfhsi_v3_init);
+module_exit(cfhsi_v3_exit);