From aded1bd942166863cd377596a917f2c31ea4737b Mon Sep 17 00:00:00 2001 From: Pawel Szyszuk Date: Mon, 18 Apr 2011 15:15:20 +0100 Subject: drivers: hsi: Add CAIF-HSI glue layer Change-Id: Id0b4672be6a34cf7fca2494ebb6728b679ad9a63 Signed-off-by: Pawel Szyszuk Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21042 Reviewed-by: Jonas ABERG Reviewed-by: Henrik CARLING Tested-by: Henrik CARLING --- drivers/hsi/clients/Kconfig | 6 + drivers/hsi/clients/Makefile | 1 + drivers/hsi/clients/cfhsi.c | 318 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 drivers/hsi/clients/cfhsi.c diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig index 3bacd275f47..46eef1f77fd 100644 --- a/drivers/hsi/clients/Kconfig +++ b/drivers/hsi/clients/Kconfig @@ -11,3 +11,9 @@ config HSI_CHAR If you say Y here, you will enable the HSI/SSI character driver. This driver provides a simple character device interface for serial communication with the cellular modem over HSI/SSI bus. +config HSI_CAIF + tristate "CAIF HSI driver" + depends on HSI + default n + ---help--- + Provides HSI-CAIF glue layer diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile index 327c0e27c8b..dfe33584975 100644 --- a/drivers/hsi/clients/Makefile +++ b/drivers/hsi/clients/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_HSI_CHAR) += hsi_char.o +obj-$(CONFIG_HSI_CAIF) += cfhsi.o 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 + * License terms: GNU General Public License (GPL) version 2. + */ + +#include +#include +#include +#include +#include + +#include + +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Martensson"); +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); -- cgit v1.2.3