From cb10b9071652fd71d8af8177cbf4943ab63c78ab Mon Sep 17 00:00:00 2001 From: Carlos Chinea Date: Tue, 14 Dec 2010 10:09:37 +0000 Subject: HSI: hsi: Introducing HSI framework Adds HSI framework in to the linux kernel. High Speed Synchronous Serial Interface (HSI) is a serial interface mainly used for connecting application engines (APE) with cellular modem engines (CMT) in cellular handsets. HSI provides multiplexing for up to 16 logical channels, low-latency and full duplex communication. Change-Id: I7f20219c15151e5c36c22a581b581b832abec04d Signed-off-by: Carlos Chinea Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20575 Reviewed-by: Pawel SZYSZUK Tested-by: Pawel SZYSZUK Reviewed-by: Jonas ABERG --- include/linux/hsi/hsi.h | 389 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 include/linux/hsi/hsi.h (limited to 'include') diff --git a/include/linux/hsi/hsi.h b/include/linux/hsi/hsi.h new file mode 100644 index 00000000000..1a52623a7dd --- /dev/null +++ b/include/linux/hsi/hsi.h @@ -0,0 +1,389 @@ +/* + * hsi.h + * + * HSI core header file. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __LINUX_HSI_H__ +#define __LINUX_HSI_H__ + +#include +#include +#include +#include +#include +#include + +/* HSI message ttype */ +#define HSI_MSG_READ 0 +#define HSI_MSG_WRITE 1 + +/* HSI configuration values */ +enum { + HSI_MODE_STREAM = 1, + HSI_MODE_FRAME, +}; + +enum { + HSI_FLOW_SYNC, /* Synchronized flow */ + HSI_FLOW_PIPE, /* Pipelined flow */ +}; + +enum { + HSI_ARB_RR, /* Round-robin arbitration */ + HSI_ARB_PRIO, /* Channel priority arbitration */ +}; + +#define HSI_MAX_CHANNELS 16 + +/* HSI message status codes */ +enum { + HSI_STATUS_COMPLETED, /* Message transfer is completed */ + HSI_STATUS_PENDING, /* Message pending to be read/write (POLL) */ + HSI_STATUS_PROCEEDING, /* Message transfer is ongoing */ + HSI_STATUS_QUEUED, /* Message waiting to be served */ + HSI_STATUS_ERROR, /* Error when message transfer was ongoing */ +}; + +/* HSI port event codes */ +enum { + HSI_EVENT_START_RX, + HSI_EVENT_STOP_RX, +}; + +/** + * struct hsi_config - Configuration for RX/TX HSI modules + * @mode: Bit transmission mode (STREAM or FRAME) + * @channels: Number of channels to use [1..16] + * @speed: Max bit transmission speed (Kbit/s) + * @flow: RX flow type (SYNCHRONIZED or PIPELINE) + * @arb_mode: Arbitration mode for TX frame (Round robin, priority) + */ +struct hsi_config { + unsigned int mode; + unsigned int channels; + unsigned int speed; + union { + unsigned int flow; /* RX only */ + unsigned int arb_mode; /* TX only */ + }; +}; + +/** + * struct hsi_board_info - HSI client board info + * @name: Name for the HSI device + * @hsi_id: HSI controller id where the client sits + * @port: Port number in the controller where the client sits + * @tx_cfg: HSI TX configuration + * @rx_cfg: HSI RX configuration + * @platform_data: Platform related data + * @archdata: Architecture-dependent device data + */ +struct hsi_board_info { + const char *name; + int hsi_id; + unsigned int port; + struct hsi_config tx_cfg; + struct hsi_config rx_cfg; + void *platform_data; + struct dev_archdata *archdata; +}; + +#ifdef CONFIG_HSI_BOARDINFO +extern int hsi_register_board_info(struct hsi_board_info const *info, + unsigned int len); +#else +static inline int hsi_register_board_info(struct hsi_board_info const *info, + unsigned int len) +{ + return 0; +} +#endif /* CONFIG_HSI_BOARDINFO */ + +/** + * struct hsi_client - HSI client attached to an HSI port + * @device: Driver model representation of the device + * @tx_cfg: HSI TX configuration + * @rx_cfg: HSI RX configuration + * @hsi_start_rx: Called after incoming wake line goes high + * @hsi_stop_rx: Called after incoming wake line goes low + */ +struct hsi_client { + struct device device; + struct hsi_config tx_cfg; + struct hsi_config rx_cfg; + void (*hsi_start_rx)(struct hsi_client *cl); + void (*hsi_stop_rx)(struct hsi_client *cl); + /* private: */ + unsigned int pclaimed:1; + struct list_head link; +}; + +#define to_hsi_client(dev) container_of(dev, struct hsi_client, device) + +static inline void hsi_client_set_drvdata(struct hsi_client *cl, void *data) +{ + dev_set_drvdata(&cl->device, data); +} + +static inline void *hsi_client_drvdata(struct hsi_client *cl) +{ + return dev_get_drvdata(&cl->device); +} + +/** + * struct hsi_client_driver - Driver associated to an HSI client + * @driver: Driver model representation of the driver + */ +struct hsi_client_driver { + struct device_driver driver; +}; + +#define to_hsi_client_driver(drv) container_of(drv, struct hsi_client_driver,\ + driver) + +int hsi_register_client_driver(struct hsi_client_driver *drv); + +static inline void hsi_unregister_client_driver(struct hsi_client_driver *drv) +{ + driver_unregister(&drv->driver); +} + +/** + * struct hsi_msg - HSI message descriptor + * @link: Free to use by the current descriptor owner + * @cl: HSI device client that issues the transfer + * @sgt: Head of the scatterlist array + * @context: Client context data associated to the transfer + * @complete: Transfer completion callback + * @destructor: Destructor to free resources when flushing + * @status: Status of the transfer when completed + * @actual_len: Actual length of data transfered on completion + * @channel: Channel were to TX/RX the message + * @ttype: Transfer type (TX if set, RX otherwise) + * @break_frame: if true HSI will send/receive a break frame (FRAME MODE) + */ +struct hsi_msg { + struct list_head link; + struct hsi_client *cl; + struct sg_table sgt; + void *context; + + void (*complete)(struct hsi_msg *msg); + void (*destructor)(struct hsi_msg *msg); + + int status; + unsigned int actual_len; + unsigned int channel; + unsigned int ttype:1; + unsigned int break_frame:1; +}; + +struct hsi_msg *hsi_alloc_msg(unsigned int n_frag, gfp_t flags); +void hsi_free_msg(struct hsi_msg *msg); + +/** + * struct hsi_port - HSI port device + * @device: Driver model representation of the device + * @tx_cfg: Current TX path configuration + * @rx_cfg: Current RX path configuration + * @num: Port number + * @shared: Set when port can be shared by different clients + * @claimed: Reference count of clients which claimed the port + * @lock: Serialize port claim + * @async: Asynchronous transfer callback + * @setup: Callback to set the HSI client configuration + * @flush: Callback to clean the HW state and destroy all pending transfers + * @start_tx: Callback to inform that a client wants to TX data + * @stop_tx: Callback to inform that a client no longer wishes to TX data + * @release: Callback to inform that a client no longer uses the port + * @clients: List of hsi_clients using the port. + * @clock: Lock to serialize access to the clients list. + */ +struct hsi_port { + struct device device; + struct hsi_config tx_cfg; + struct hsi_config rx_cfg; + unsigned int num; + unsigned int shared:1; + int claimed; + struct mutex lock; + int (*async)(struct hsi_msg *msg); + int (*setup)(struct hsi_client *cl); + int (*flush)(struct hsi_client *cl); + int (*start_tx)(struct hsi_client *cl); + int (*stop_tx)(struct hsi_client *cl); + int (*release)(struct hsi_client *cl); + struct list_head clients; + spinlock_t clock; +}; + +#define to_hsi_port(dev) container_of(dev, struct hsi_port, device) +#define hsi_get_port(cl) to_hsi_port((cl)->device.parent) + +void hsi_event(struct hsi_port *port, unsigned int event); +int hsi_claim_port(struct hsi_client *cl, unsigned int share); +void hsi_release_port(struct hsi_client *cl); + +static inline int hsi_port_claimed(struct hsi_client *cl) +{ + return cl->pclaimed; +} + +static inline void hsi_port_set_drvdata(struct hsi_port *port, void *data) +{ + dev_set_drvdata(&port->device, data); +} + +static inline void *hsi_port_drvdata(struct hsi_port *port) +{ + return dev_get_drvdata(&port->device); +} + +/** + * struct hsi_controller - HSI controller device + * @device: Driver model representation of the device + * @owner: Pointer to the module owning the controller + * @id: HSI controller ID + * @num_ports: Number of ports in the HSI controller + * @port: Array of HSI ports + */ +struct hsi_controller { + struct device device; + struct module *owner; + int id; + unsigned int num_ports; + struct hsi_port *port; +}; + +#define to_hsi_controller(dev) container_of(dev, struct hsi_controller, device) + +struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags); +void hsi_free_controller(struct hsi_controller *hsi); +int hsi_register_controller(struct hsi_controller *hsi); +void hsi_unregister_controller(struct hsi_controller *hsi); + +static inline void hsi_controller_set_drvdata(struct hsi_controller *hsi, + void *data) +{ + dev_set_drvdata(&hsi->device, data); +} + +static inline void *hsi_controller_drvdata(struct hsi_controller *hsi) +{ + return dev_get_drvdata(&hsi->device); +} + +static inline struct hsi_port *hsi_find_port_num(struct hsi_controller *hsi, + unsigned int num) +{ + return (num < hsi->num_ports) ? &hsi->port[num] : NULL; +} + +/* + * API for HSI clients + */ +int hsi_async(struct hsi_client *cl, struct hsi_msg *msg); + +/** + * hsi_setup - Configure the client's port + * @cl: Pointer to the HSI client + * + * When sharing ports, clients should either relay on a single + * client setup or have the same setup for all of them. + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_setup(struct hsi_client *cl) +{ + if (!hsi_port_claimed(cl)) + return -EACCES; + return hsi_get_port(cl)->setup(cl); +} + +/** + * hsi_flush - Flush all pending transactions on the client's port + * @cl: Pointer to the HSI client + * + * This function will destroy all pending hsi_msg in the port and reset + * the HW port so it is ready to receive and transmit from a clean state. + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_flush(struct hsi_client *cl) +{ + if (!hsi_port_claimed(cl)) + return -EACCES; + return hsi_get_port(cl)->flush(cl); +} + +/** + * hsi_async_read - Submit a read transfer + * @cl: Pointer to the HSI client + * @msg: HSI message descriptor of the transfer + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_async_read(struct hsi_client *cl, struct hsi_msg *msg) +{ + msg->ttype = HSI_MSG_READ; + return hsi_async(cl, msg); +} + +/** + * hsi_async_write - Submit a write transfer + * @cl: Pointer to the HSI client + * @msg: HSI message descriptor of the transfer + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_async_write(struct hsi_client *cl, struct hsi_msg *msg) +{ + msg->ttype = HSI_MSG_WRITE; + return hsi_async(cl, msg); +} + +/** + * hsi_start_tx - Signal the port that the client wants to start a TX + * @cl: Pointer to the HSI client + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_start_tx(struct hsi_client *cl) +{ + if (!hsi_port_claimed(cl)) + return -EACCES; + return hsi_get_port(cl)->start_tx(cl); +} + +/** + * hsi_stop_tx - Signal the port that the client no longer wants to transmit + * @cl: Pointer to the HSI client + * + * Return -errno on failure, 0 on success + */ +static inline int hsi_stop_tx(struct hsi_client *cl) +{ + if (!hsi_port_claimed(cl)) + return -EACCES; + return hsi_get_port(cl)->stop_tx(cl); +} +#endif /* __LINUX_HSI_H__ */ -- cgit v1.2.3 From 2f5f0e5e2ba0abc468875c69deb55314a8a9f96e Mon Sep 17 00:00:00 2001 From: Andras Domokos Date: Tue, 14 Dec 2010 10:09:40 +0000 Subject: HSI: hsi_char: Add HSI char device driver Add HSI char device driver to the kernel. Change-Id: I58a6e1526271781c4532202b4688ee239e6fbc58 Signed-off-by: Andras Domokos Signed-off-by: Carlos Chinea Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20578 Reviewed-by: Pawel SZYSZUK Tested-by: Pawel SZYSZUK Reviewed-by: Jonas ABERG --- drivers/hsi/clients/hsi_char.c | 1094 ++++++++++++++++++++++++++++++++++++++++ include/linux/hsi/hsi_char.h | 65 +++ 2 files changed, 1159 insertions(+) create mode 100644 drivers/hsi/clients/hsi_char.c create mode 100644 include/linux/hsi/hsi_char.h (limited to 'include') diff --git a/drivers/hsi/clients/hsi_char.c b/drivers/hsi/clients/hsi_char.c new file mode 100644 index 00000000000..19aeb242f2a --- /dev/null +++ b/drivers/hsi/clients/hsi_char.c @@ -0,0 +1,1094 @@ +/* + * hsi-char.c + * + * HSI character device driver, implements the character device + * interface. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * + * Contact: Andras Domokos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HSI_CHAR_CHANNELS 8 +#define HSI_CHAR_DEVS 8 +#define HSI_CHAR_MSGS 4 + +#define HSI_CHST_UNAVAIL 0 /* SBZ! */ +#define HSI_CHST_AVAIL 1 + +#define HSI_CHST_CLOSED (0 << 4) +#define HSI_CHST_CLOSING (1 << 4) +#define HSI_CHST_OPENING (2 << 4) +#define HSI_CHST_OPENED (3 << 4) + +#define HSI_CHST_READOFF (0 << 8) +#define HSI_CHST_READON (1 << 8) +#define HSI_CHST_READING (2 << 8) + +#define HSI_CHST_WRITEOFF (0 << 12) +#define HSI_CHST_WRITEON (1 << 12) +#define HSI_CHST_WRITING (2 << 12) + +#define HSI_CHST_OC_MASK 0xf0 +#define HSI_CHST_RD_MASK 0xf00 +#define HSI_CHST_WR_MASK 0xf000 + +#define HSI_CHST_OC(c) ((c)->state & HSI_CHST_OC_MASK) +#define HSI_CHST_RD(c) ((c)->state & HSI_CHST_RD_MASK) +#define HSI_CHST_WR(c) ((c)->state & HSI_CHST_WR_MASK) + +#define HSI_CHST_OC_SET(c, v) \ + do { \ + (c)->state &= ~HSI_CHST_OC_MASK; \ + (c)->state |= v; \ + } while (0); + +#define HSI_CHST_RD_SET(c, v) \ + do { \ + (c)->state &= ~HSI_CHST_RD_MASK; \ + (c)->state |= v; \ + } while (0); + +#define HSI_CHST_WR_SET(c, v) \ + do { \ + (c)->state &= ~HSI_CHST_WR_MASK; \ + (c)->state |= v; \ + } while (0); + +#define HSI_CHAR_POLL_RST (-1) +#define HSI_CHAR_POLL_OFF 0 +#define HSI_CHAR_POLL_ON 1 + +#define HSI_CHAR_RX 0 +#define HSI_CHAR_TX 1 + +struct hsi_char_channel { + unsigned int ch; + unsigned int state; + int wlrefcnt; + int rxpoll; + struct hsi_client *cl; + struct list_head free_msgs_list; + struct list_head rx_msgs_queue; + struct list_head tx_msgs_queue; + int poll_event; + spinlock_t lock; + struct fasync_struct *async_queue; + wait_queue_head_t rx_wait; + wait_queue_head_t tx_wait; +}; + +struct hsi_char_client_data { + atomic_t refcnt; + int attached; + atomic_t breq; + struct hsi_char_channel channels[HSI_CHAR_DEVS]; +}; + +static unsigned int max_data_size = 0x1000; +module_param(max_data_size, uint, 1); +MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)"); + +static int channels_map[HSI_CHAR_DEVS] = {0, -1, -1 , -1, -1, -1, -1, -1}; +module_param_array(channels_map, int, NULL, 0); +MODULE_PARM_DESC(channels_map, "Array of HSI channels ([0...7]) to be probed"); + +static dev_t hsi_char_dev; +static struct hsi_char_client_data hsi_char_cl_data; + +static inline void hsi_char_msg_free(struct hsi_msg *msg) +{ + msg->complete = NULL; + msg->destructor = NULL; + kfree(sg_virt(msg->sgt.sgl)); + hsi_free_msg(msg); +} + +static inline void hsi_char_msgs_free(struct hsi_char_channel *channel) +{ + struct hsi_msg *msg, *tmp; + + list_for_each_entry_safe(msg, tmp, &channel->free_msgs_list, link) { + list_del(&msg->link); + hsi_char_msg_free(msg); + } + list_for_each_entry_safe(msg, tmp, &channel->rx_msgs_queue, link) { + list_del(&msg->link); + hsi_char_msg_free(msg); + } + list_for_each_entry_safe(msg, tmp, &channel->tx_msgs_queue, link) { + list_del(&msg->link); + hsi_char_msg_free(msg); + } +} + +static inline struct hsi_msg *hsi_char_msg_alloc(unsigned int alloc_size) +{ + struct hsi_msg *msg; + void *buf; + + msg = hsi_alloc_msg(1, GFP_KERNEL); + if (!msg) + goto out; + buf = kmalloc(alloc_size, GFP_KERNEL); + if (!buf) { + hsi_free_msg(msg); + goto out; + } + sg_init_one(msg->sgt.sgl, buf, alloc_size); + msg->context = buf; + return msg; +out: + return NULL; +} + +static inline int hsi_char_msgs_alloc(struct hsi_char_channel *channel) +{ + struct hsi_msg *msg; + int i; + + for (i = 0; i < HSI_CHAR_MSGS; i++) { + msg = hsi_char_msg_alloc(max_data_size); + if (!msg) + goto out; + msg->channel = channel->ch; + list_add_tail(&msg->link, &channel->free_msgs_list); + } + return 0; +out: + hsi_char_msgs_free(channel); + + return -ENOMEM; +} + +static int _hsi_char_release(struct hsi_char_channel *channel, int remove) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(channel->cl); + int ret = 0, refcnt; + + spin_lock_bh(&channel->lock); + if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) + goto out; + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSING); + spin_unlock_bh(&channel->lock); + + while (channel->wlrefcnt > 0) { + hsi_stop_tx(channel->cl); + channel->wlrefcnt--; + } + + if (channel->rxpoll == HSI_CHAR_POLL_ON) + channel->poll_event |= POLLERR; + + wake_up_interruptible(&channel->rx_wait); + wake_up_interruptible(&channel->tx_wait); + + refcnt = atomic_dec_return(&cl_data->refcnt); + if (!refcnt) { + hsi_flush(channel->cl); + hsi_release_port(channel->cl); + cl_data->attached = 0; + } + hsi_char_msgs_free(channel); + + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED); + HSI_CHST_RD_SET(channel, HSI_CHST_READOFF); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); +out: + if (remove) + channel->cl = NULL; + spin_unlock_bh(&channel->lock); + + return ret; +} + +static int __devinit hsi_char_probe(struct device *dev) +{ + struct hsi_char_client_data *cl_data = &hsi_char_cl_data; + struct hsi_char_channel *channel = cl_data->channels; + struct hsi_client *cl = to_hsi_client(dev); + int i; + + for (i = 0; i < HSI_CHAR_DEVS; i++, channel++) { + if (channel->state == HSI_CHST_AVAIL) + channel->cl = cl; + } + cl->hsi_start_rx = NULL; + cl->hsi_stop_rx = NULL; + atomic_set(&cl_data->refcnt, 0); + atomic_set(&cl_data->breq, 1); + cl_data->attached = 0; + hsi_client_set_drvdata(cl, cl_data); + + return 0; +} + +static int __devexit hsi_char_remove(struct device *dev) +{ + struct hsi_client *cl = to_hsi_client(dev); + struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl); + struct hsi_char_channel *channel = cl_data->channels; + int i; + + for (i = 0; i < HSI_CHAR_DEVS; i++, channel++) { + if (!(channel->state & HSI_CHST_AVAIL)) + continue; + _hsi_char_release(channel, 1); + } + + return 0; +} + +static inline unsigned int hsi_char_msg_len_get(struct hsi_msg *msg) +{ + return msg->sgt.sgl->length; +} + +static inline void hsi_char_msg_len_set(struct hsi_msg *msg, unsigned int len) +{ + msg->sgt.sgl->length = len; +} + +static void hsi_char_data_available(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + int ret; + + if (msg->status == HSI_STATUS_ERROR) { + ret = hsi_async_read(channel->cl, msg); + if (ret < 0) { + list_add_tail(&msg->link, &channel->free_msgs_list); + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->free_msgs_list); + channel->rxpoll = HSI_CHAR_POLL_OFF; + spin_unlock_bh(&channel->lock); + } + } else { + spin_lock_bh(&channel->lock); + channel->rxpoll = HSI_CHAR_POLL_OFF; + channel->poll_event |= (POLLIN | POLLRDNORM); + spin_unlock_bh(&channel->lock); + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->free_msgs_list); + spin_unlock_bh(&channel->lock); + wake_up_interruptible(&channel->rx_wait); + } +} + +static void hsi_char_rx_completed(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->rx_msgs_queue); + spin_unlock_bh(&channel->lock); + wake_up_interruptible(&channel->rx_wait); +} + +static void hsi_char_rx_msg_destructor(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->free_msgs_list); + HSI_CHST_RD_SET(channel, HSI_CHST_READOFF); + spin_unlock_bh(&channel->lock); +} + +static void hsi_char_rx_poll_destructor(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->free_msgs_list); + channel->rxpoll = HSI_CHAR_POLL_RST; + spin_unlock_bh(&channel->lock); +} + +static int hsi_char_rx_poll(struct hsi_char_channel *channel) +{ + struct hsi_msg *msg; + int ret = 0; + + spin_lock_bh(&channel->lock); + if (list_empty(&channel->free_msgs_list)) { + ret = -ENOMEM; + goto out; + } + if (channel->rxpoll == HSI_CHAR_POLL_ON) + goto out; + msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link); + list_del(&msg->link); + channel->rxpoll = HSI_CHAR_POLL_ON; + spin_unlock_bh(&channel->lock); + hsi_char_msg_len_set(msg, 0); + msg->complete = hsi_char_data_available; + msg->destructor = hsi_char_rx_poll_destructor; + /* don't touch msg->context! */ + ret = hsi_async_read(channel->cl, msg); + spin_lock_bh(&channel->lock); + if (ret < 0) { + list_add_tail(&msg->link, &channel->free_msgs_list); + channel->rxpoll = HSI_CHAR_POLL_OFF; + goto out; + } +out: + spin_unlock_bh(&channel->lock); + + return ret; +} + +static void hsi_char_tx_completed(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->tx_msgs_queue); + channel->poll_event |= (POLLOUT | POLLWRNORM); + spin_unlock_bh(&channel->lock); + wake_up_interruptible(&channel->tx_wait); +} + +static void hsi_char_tx_msg_destructor(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + struct hsi_char_channel *channel = cl_data->channels + msg->channel; + + spin_lock_bh(&channel->lock); + list_add_tail(&msg->link, &channel->free_msgs_list); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); + spin_unlock_bh(&channel->lock); +} + +static void hsi_char_rx_poll_rst(struct hsi_client *cl) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl); + struct hsi_char_channel *channel = cl_data->channels; + int i; + + for (i = 0; i < HSI_CHAR_DEVS; i++, channel++) { + if ((HSI_CHST_OC(channel) == HSI_CHST_OPENED) && + (channel->rxpoll == HSI_CHAR_POLL_RST)) + hsi_char_rx_poll(channel); + } +} + +static void hsi_char_reset(struct hsi_client *cl) +{ + hsi_flush(cl); + hsi_char_rx_poll_rst(cl); +} + +static void hsi_char_rx_cancel(struct hsi_char_channel *channel) +{ + hsi_flush(channel->cl); + hsi_char_rx_poll_rst(channel->cl); +} + +static void hsi_char_tx_cancel(struct hsi_char_channel *channel) +{ + hsi_flush(channel->cl); + hsi_char_rx_poll_rst(channel->cl); +} + +static void hsi_char_bcast_break(struct hsi_client *cl) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl); + struct hsi_char_channel *channel = cl_data->channels; + int i; + + for (i = 0; i < HSI_CHAR_DEVS; i++, channel++) { + if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) + continue; + channel->poll_event |= POLLPRI; + wake_up_interruptible(&channel->rx_wait); + wake_up_interruptible(&channel->tx_wait); + } +} + +static void hsi_char_break_received(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + int ret; + + hsi_char_bcast_break(msg->cl); + ret = hsi_async_read(msg->cl, msg); + if (ret < 0) { + hsi_free_msg(msg); + atomic_inc(&cl_data->breq); + } +} + +static void hsi_char_break_req_destructor(struct hsi_msg *msg) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(msg->cl); + + hsi_free_msg(msg); + atomic_inc(&cl_data->breq); +} + +static int hsi_char_break_request(struct hsi_client *cl) +{ + struct hsi_char_client_data *cl_data = hsi_client_drvdata(cl); + struct hsi_msg *msg; + int ret = 0; + + if (!atomic_dec_and_test(&cl_data->breq)) { + atomic_inc(&cl_data->breq); + return -EBUSY; + } + msg = hsi_alloc_msg(0, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->break_frame = 1; + msg->complete = hsi_char_break_received; + msg->destructor = hsi_char_break_req_destructor; + ret = hsi_async_read(cl, msg); + if (ret < 0) + hsi_free_msg(msg); + + return ret; +} + +static int hsi_char_break_send(struct hsi_client *cl) +{ + struct hsi_msg *msg; + int ret = 0; + + msg = hsi_alloc_msg(0, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + msg->break_frame = 1; + msg->complete = hsi_free_msg; + msg->destructor = hsi_free_msg; + ret = hsi_async_write(cl, msg); + if (ret < 0) + hsi_free_msg(msg); + + return ret; +} + +static inline int ssi_check_common_cfg(struct hsi_config *cfg) +{ + if ((cfg->mode != HSI_MODE_STREAM) && (cfg->mode != HSI_MODE_FRAME)) + return -EINVAL; + if ((cfg->channels == 0) || (cfg->channels > HSI_CHAR_CHANNELS)) + return -EINVAL; + if (cfg->channels & (cfg->channels - 1)) + return -EINVAL; + + return 0; +} + +static inline int ssi_check_rx_cfg(struct hsi_config *cfg) +{ + int ret; + + ret = ssi_check_common_cfg(cfg); + if (ret < 0) + return ret; + if ((cfg->flow != HSI_FLOW_SYNC) && (cfg->flow != HSI_FLOW_PIPE)) + return -EINVAL; + + return 0; +} + +static inline int ssi_check_tx_cfg(struct hsi_config *cfg) +{ + int ret; + + ret = ssi_check_common_cfg(cfg); + if (ret < 0) + return ret; + if ((cfg->arb_mode != HSI_ARB_RR) && (cfg->arb_mode != HSI_ARB_PRIO)) + return -EINVAL; + + return 0; +} + +static inline int hsi_char_cfg_set(struct hsi_client *cl, + struct hsi_config *cfg, int dir) +{ + struct hsi_config *rxtx_cfg; + int ret = 0; + + if (dir == HSI_CHAR_RX) { + rxtx_cfg = &cl->rx_cfg; + ret = ssi_check_rx_cfg(cfg); + } else { + rxtx_cfg = &cl->tx_cfg; + ret = ssi_check_tx_cfg(cfg); + } + if (ret < 0) + return ret; + + *rxtx_cfg = *cfg; + ret = hsi_setup(cl); + if (ret < 0) + return ret; + + if ((dir == HSI_CHAR_RX) && (cfg->mode == HSI_MODE_FRAME)) + hsi_char_break_request(cl); + + return ret; +} + +static inline void hsi_char_cfg_get(struct hsi_client *cl, + struct hsi_config *cfg, int dir) +{ + struct hsi_config *rxtx_cfg; + + if (dir == HSI_CHAR_RX) + rxtx_cfg = &cl->rx_cfg; + else + rxtx_cfg = &cl->tx_cfg; + *cfg = *rxtx_cfg; +} + +static inline void hsi_char_rx2icfg(struct hsi_config *cfg, + struct hsc_rx_config *rx_cfg) +{ + cfg->mode = rx_cfg->mode; + cfg->flow = rx_cfg->flow; + cfg->channels = rx_cfg->channels; + cfg->speed = 0; +} + +static inline void hsi_char_tx2icfg(struct hsi_config *cfg, + struct hsc_tx_config *tx_cfg) +{ + cfg->mode = tx_cfg->mode; + cfg->channels = tx_cfg->channels; + cfg->speed = tx_cfg->speed; + cfg->arb_mode = tx_cfg->arb_mode; +} + +static inline void hsi_char_rx2ecfg(struct hsc_rx_config *rx_cfg, + struct hsi_config *cfg) +{ + rx_cfg->mode = cfg->mode; + rx_cfg->flow = cfg->flow; + rx_cfg->channels = cfg->channels; +} + +static inline void hsi_char_tx2ecfg(struct hsc_tx_config *tx_cfg, + struct hsi_config *cfg) +{ + tx_cfg->mode = cfg->mode; + tx_cfg->channels = cfg->channels; + tx_cfg->speed = cfg->speed; + tx_cfg->arb_mode = cfg->arb_mode; +} + +static ssize_t hsi_char_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + struct hsi_char_channel *channel = file->private_data; + struct hsi_msg *msg = NULL; + ssize_t ret; + + if (len == 0) { + channel->poll_event &= ~POLLPRI; + return 0; + } + channel->poll_event &= ~POLLPRI; + + if (!IS_ALIGNED(len, sizeof(u32))) + return -EINVAL; + + if (len > max_data_size) + len = max_data_size; + + spin_lock_bh(&channel->lock); + if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) { + ret = -ENODEV; + goto out; + } + if (HSI_CHST_RD(channel) != HSI_CHST_READOFF) { + ret = -EBUSY; + goto out; + } + if (channel->ch >= channel->cl->rx_cfg.channels) { + ret = -ENODEV; + goto out; + } + if (list_empty(&channel->free_msgs_list)) { + ret = -ENOMEM; + goto out; + } + msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link); + list_del(&msg->link); + spin_unlock_bh(&channel->lock); + hsi_char_msg_len_set(msg, len); + msg->complete = hsi_char_rx_completed; + msg->destructor = hsi_char_rx_msg_destructor; + ret = hsi_async_read(channel->cl, msg); + spin_lock_bh(&channel->lock); + if (ret < 0) + goto out; + HSI_CHST_RD_SET(channel, HSI_CHST_READING); + msg = NULL; + + for ( ; ; ) { + DEFINE_WAIT(wait); + + if (!list_empty(&channel->rx_msgs_queue)) { + msg = list_first_entry(&channel->rx_msgs_queue, + struct hsi_msg, link); + HSI_CHST_RD_SET(channel, HSI_CHST_READOFF); + channel->poll_event &= ~(POLLIN | POLLRDNORM); + list_del(&msg->link); + spin_unlock_bh(&channel->lock); + if (msg->status == HSI_STATUS_ERROR) { + ret = -EIO; + } else { + ret = copy_to_user((void __user *)buf, + msg->context, + hsi_char_msg_len_get(msg)); + if (ret) + ret = -EFAULT; + else + ret = hsi_char_msg_len_get(msg); + } + spin_lock_bh(&channel->lock); + break; + } else if (signal_pending(current)) { + spin_unlock_bh(&channel->lock); + hsi_char_rx_cancel(channel); + spin_lock_bh(&channel->lock); + HSI_CHST_RD_SET(channel, HSI_CHST_READOFF); + ret = -EINTR; + break; + } else if ((HSI_CHST_OC(channel) == HSI_CHST_CLOSING) || + (HSI_CHST_OC(channel) == HSI_CHST_CLOSING)) { + ret = -EIO; + break; + } + prepare_to_wait(&channel->rx_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_bh(&channel->lock); + + schedule(); + + spin_lock_bh(&channel->lock); + finish_wait(&channel->rx_wait, &wait); + } +out: + if (msg) + list_add_tail(&msg->link, &channel->free_msgs_list); + spin_unlock_bh(&channel->lock); + + return ret; +} + +static ssize_t hsi_char_write(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct hsi_char_channel *channel = file->private_data; + struct hsi_msg *msg = NULL; + ssize_t ret; + + if ((len == 0) || !IS_ALIGNED(len, sizeof(u32))) + return -EINVAL; + + if (len > max_data_size) + len = max_data_size; + + spin_lock_bh(&channel->lock); + if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) { + ret = -ENODEV; + goto out; + } + if (HSI_CHST_WR(channel) != HSI_CHST_WRITEOFF) { + ret = -EBUSY; + goto out; + } + if (channel->ch >= channel->cl->tx_cfg.channels) { + ret = -ENODEV; + goto out; + } + if (list_empty(&channel->free_msgs_list)) { + ret = -ENOMEM; + goto out; + } + msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link); + list_del(&msg->link); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEON); + spin_unlock_bh(&channel->lock); + + if (copy_from_user(msg->context, (void __user *)buf, len)) { + spin_lock_bh(&channel->lock); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); + ret = -EFAULT; + goto out; + } + + hsi_char_msg_len_set(msg, len); + msg->complete = hsi_char_tx_completed; + msg->destructor = hsi_char_tx_msg_destructor; + channel->poll_event &= ~(POLLOUT | POLLWRNORM); + ret = hsi_async_write(channel->cl, msg); + spin_lock_bh(&channel->lock); + if (ret < 0) { + channel->poll_event |= (POLLOUT | POLLWRNORM); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); + goto out; + } + HSI_CHST_WR_SET(channel, HSI_CHST_WRITING); + msg = NULL; + + for ( ; ; ) { + DEFINE_WAIT(wait); + + if (!list_empty(&channel->tx_msgs_queue)) { + msg = list_first_entry(&channel->tx_msgs_queue, + struct hsi_msg, link); + list_del(&msg->link); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); + if (msg->status == HSI_STATUS_ERROR) + ret = -EIO; + else + ret = hsi_char_msg_len_get(msg); + break; + } else if (signal_pending(current)) { + spin_unlock_bh(&channel->lock); + hsi_char_tx_cancel(channel); + spin_lock_bh(&channel->lock); + HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF); + ret = -EINTR; + break; + } else if ((HSI_CHST_OC(channel) == HSI_CHST_CLOSING) || + (HSI_CHST_OC(channel) == HSI_CHST_CLOSING)) { + ret = -EIO; + break; + } + prepare_to_wait(&channel->tx_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_bh(&channel->lock); + + schedule(); + + spin_lock_bh(&channel->lock); + finish_wait(&channel->tx_wait, &wait); + } +out: + if (msg) + list_add_tail(&msg->link, &channel->free_msgs_list); + + spin_unlock_bh(&channel->lock); + + return ret; +} + +static unsigned int hsi_char_poll(struct file *file, poll_table *wait) +{ + struct hsi_char_channel *channel = file->private_data; + unsigned int ret; + + spin_lock_bh(&channel->lock); + if ((HSI_CHST_OC(channel) != HSI_CHST_OPENED) || + (channel->ch >= channel->cl->rx_cfg.channels)) { + spin_unlock_bh(&channel->lock); + return -ENODEV; + } + poll_wait(file, &channel->rx_wait, wait); + poll_wait(file, &channel->tx_wait, wait); + ret = channel->poll_event; + spin_unlock_bh(&channel->lock); + hsi_char_rx_poll(channel); + + return ret; +} + +static long hsi_char_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct hsi_char_channel *channel = file->private_data; + unsigned int state; + struct hsi_config cfg; + struct hsc_rx_config rx_cfg; + struct hsc_tx_config tx_cfg; + long ret = 0; + + if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) + return -ENODEV; + + switch (cmd) { + case HSC_RESET: + hsi_char_reset(channel->cl); + break; + case HSC_SET_PM: + if (copy_from_user(&state, (void __user *)arg, sizeof(state))) + return -EFAULT; + if (state == HSC_PM_DISABLE) { + ret = hsi_start_tx(channel->cl); + if (!ret) + channel->wlrefcnt++; + } else if ((state == HSC_PM_ENABLE) + && (channel->wlrefcnt > 0)) { + ret = hsi_stop_tx(channel->cl); + if (!ret) + channel->wlrefcnt--; + } else { + ret = -EINVAL; + } + break; + case HSC_SEND_BREAK: + return hsi_char_break_send(channel->cl); + case HSC_SET_RX: + if (copy_from_user(&rx_cfg, (void __user *)arg, sizeof(rx_cfg))) + return -EFAULT; + hsi_char_rx2icfg(&cfg, &rx_cfg); + return hsi_char_cfg_set(channel->cl, &cfg, HSI_CHAR_RX); + case HSC_GET_RX: + hsi_char_cfg_get(channel->cl, &cfg, HSI_CHAR_RX); + hsi_char_rx2ecfg(&rx_cfg, &cfg); + if (copy_to_user((void __user *)arg, &rx_cfg, sizeof(rx_cfg))) + return -EFAULT; + break; + case HSC_SET_TX: + if (copy_from_user(&tx_cfg, (void __user *)arg, sizeof(tx_cfg))) + return -EFAULT; + hsi_char_tx2icfg(&cfg, &tx_cfg); + return hsi_char_cfg_set(channel->cl, &cfg, HSI_CHAR_TX); + case HSC_GET_TX: + hsi_char_cfg_get(channel->cl, &cfg, HSI_CHAR_TX); + hsi_char_tx2ecfg(&tx_cfg, &cfg); + if (copy_to_user((void __user *)arg, &tx_cfg, sizeof(tx_cfg))) + return -EFAULT; + break; + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +static int hsi_char_open(struct inode *inode, struct file *file) +{ + struct hsi_char_client_data *cl_data = &hsi_char_cl_data; + struct hsi_char_channel *channel = cl_data->channels + iminor(inode); + int ret = 0, refcnt; + + spin_lock_bh(&channel->lock); + if ((channel->state == HSI_CHST_UNAVAIL) || (!channel->cl)) { + ret = -ENODEV; + goto out; + } + if (HSI_CHST_OC(channel) != HSI_CHST_CLOSED) { + ret = -EBUSY; + goto out; + } + HSI_CHST_OC_SET(channel, HSI_CHST_OPENING); + spin_unlock_bh(&channel->lock); + + refcnt = atomic_inc_return(&cl_data->refcnt); + if (refcnt == 1) { + if (cl_data->attached) { + atomic_dec(&cl_data->refcnt); + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED); + ret = -EBUSY; + goto out; + } + ret = hsi_claim_port(channel->cl, 0); + if (ret < 0) { + atomic_dec(&cl_data->refcnt); + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED); + goto out; + } + hsi_setup(channel->cl); + } else if (!cl_data->attached) { + atomic_dec(&cl_data->refcnt); + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED); + ret = -ENODEV; + goto out; + } + ret = hsi_char_msgs_alloc(channel); + + if (ret < 0) { + refcnt = atomic_dec_return(&cl_data->refcnt); + if (!refcnt) + hsi_release_port(channel->cl); + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_CLOSED); + goto out; + } + if (refcnt == 1) + cl_data->attached = 1; + channel->wlrefcnt = 0; + channel->rxpoll = HSI_CHAR_POLL_OFF; + channel->poll_event = (POLLOUT | POLLWRNORM); + file->private_data = channel; + spin_lock_bh(&channel->lock); + HSI_CHST_OC_SET(channel, HSI_CHST_OPENED); +out: + spin_unlock_bh(&channel->lock); + + return ret; +} + +static int hsi_char_release(struct inode *inode, struct file *file) +{ + struct hsi_char_channel *channel = file->private_data; + return _hsi_char_release(channel, 0); +} + +static int hsi_char_fasync(int fd, struct file *file, int on) +{ + struct hsi_char_channel *channel = file->private_data; + + if (fasync_helper(fd, file, on, &channel->async_queue) < 0) + return -EIO; + + return 0; +} + +static const struct file_operations hsi_char_fops = { + .owner = THIS_MODULE, + .read = hsi_char_read, + .write = hsi_char_write, + .poll = hsi_char_poll, + .unlocked_ioctl = hsi_char_ioctl, + .open = hsi_char_open, + .release = hsi_char_release, + .fasync = hsi_char_fasync, +}; + +static struct hsi_client_driver hsi_char_driver = { + .driver = { + .name = "hsi_char", + .owner = THIS_MODULE, + .probe = hsi_char_probe, + .remove = hsi_char_remove, + }, +}; + +static inline void hsi_char_channel_init(struct hsi_char_channel *channel) +{ + channel->state = HSI_CHST_AVAIL; + INIT_LIST_HEAD(&channel->free_msgs_list); + init_waitqueue_head(&channel->rx_wait); + init_waitqueue_head(&channel->tx_wait); + spin_lock_init(&channel->lock); + INIT_LIST_HEAD(&channel->rx_msgs_queue); + INIT_LIST_HEAD(&channel->tx_msgs_queue); +} + +static struct cdev hsi_char_cdev; + +static int __init hsi_char_init(void) +{ + char devname[] = "hsi_char"; + struct hsi_char_client_data *cl_data = &hsi_char_cl_data; + struct hsi_char_channel *channel = cl_data->channels; + unsigned long ch_mask = 0; + unsigned int i; + int ret; + + if ((max_data_size < 4) || (max_data_size > 0x10000) || + (max_data_size & (max_data_size - 1))) { + pr_err("Invalid max read/write data size"); + return -EINVAL; + } + + for (i = 0; i < HSI_CHAR_DEVS && channels_map[i] >= 0; i++) { + if (channels_map[i] >= HSI_CHAR_DEVS) { + pr_err("Invalid HSI/SSI channel specified"); + return -EINVAL; + } + set_bit(channels_map[i], &ch_mask); + } + + if (i == 0) { + pr_err("No HSI channels available"); + return -EINVAL; + } + + memset(cl_data->channels, 0, sizeof(cl_data->channels)); + for (i = 0; i < HSI_CHAR_DEVS; i++, channel++) { + channel->ch = i; + channel->state = HSI_CHST_UNAVAIL; + if (test_bit(i, &ch_mask)) + hsi_char_channel_init(channel); + } + + ret = hsi_register_client_driver(&hsi_char_driver); + if (ret) { + pr_err("Error while registering HSI/SSI driver %d", ret); + return ret; + } + + ret = alloc_chrdev_region(&hsi_char_dev, 0, HSI_CHAR_DEVS, devname); + if (ret < 0) { + hsi_unregister_client_driver(&hsi_char_driver); + return ret; + } + + cdev_init(&hsi_char_cdev, &hsi_char_fops); + ret = cdev_add(&hsi_char_cdev, hsi_char_dev, HSI_CHAR_DEVS); + if (ret) { + unregister_chrdev_region(hsi_char_dev, HSI_CHAR_DEVS); + hsi_unregister_client_driver(&hsi_char_driver); + return ret; + } + + pr_info("HSI/SSI char device loaded\n"); + + return 0; +} +module_init(hsi_char_init); + +static void __exit hsi_char_exit(void) +{ + cdev_del(&hsi_char_cdev); + unregister_chrdev_region(hsi_char_dev, HSI_CHAR_DEVS); + hsi_unregister_client_driver(&hsi_char_driver); + pr_info("HSI char device removed\n"); +} +module_exit(hsi_char_exit); + +MODULE_AUTHOR("Andras Domokos "); +MODULE_ALIAS("hsi:hsi_char"); +MODULE_DESCRIPTION("HSI character device"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/hsi/hsi_char.h b/include/linux/hsi/hsi_char.h new file mode 100644 index 00000000000..fc498973714 --- /dev/null +++ b/include/linux/hsi/hsi_char.h @@ -0,0 +1,65 @@ +/* + * hsi_char.h + * + * Part of the HSI character device driver. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * + * Contact: Andras Domokos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + +#ifndef __HSI_CHAR_H +#define __HSI_CHAR_H + +#define HSI_CHAR_MAGIC 'k' +#define HSC_IOW(num, dtype) _IOW(HSI_CHAR_MAGIC, num, dtype) +#define HSC_IOR(num, dtype) _IOR(HSI_CHAR_MAGIC, num, dtype) +#define HSC_IOWR(num, dtype) _IOWR(HSI_CHAR_MAGIC, num, dtype) +#define HSC_IO(num) _IO(HSI_CHAR_MAGIC, num) + +#define HSC_RESET HSC_IO(16) +#define HSC_SET_PM HSC_IO(17) +#define HSC_SEND_BREAK HSC_IO(18) +#define HSC_SET_RX HSC_IOW(19, struct hsc_rx_config) +#define HSC_GET_RX HSC_IOW(20, struct hsc_rx_config) +#define HSC_SET_TX HSC_IOW(21, struct hsc_tx_config) +#define HSC_GET_TX HSC_IOW(22, struct hsc_tx_config) + +#define HSC_PM_DISABLE 0 +#define HSC_PM_ENABLE 1 + +#define HSC_MODE_STREAM 1 +#define HSC_MODE_FRAME 2 +#define HSC_FLOW_SYNC 0 +#define HSC_ARB_RR 0 +#define HSC_ARB_PRIO 1 + +struct hsc_rx_config { + uint32_t mode; + uint32_t flow; + uint32_t channels; +}; + +struct hsc_tx_config { + uint32_t mode; + uint32_t channels; + uint32_t speed; + uint32_t arb_mode; +}; + +#endif /* __HSI_CHAR_H */ -- cgit v1.2.3 From 1ab9d01373e1fb02ec680f2027aa1e95934cfff2 Mon Sep 17 00:00:00 2001 From: Andras Domokos Date: Tue, 14 Dec 2010 10:09:41 +0000 Subject: HSI: hsi_char: Add HSI char device kernel configuration Add HSI character device kernel configuration Change-Id: Ic66236e6745634850ef887b96313bbdc6e624972 Signed-off-by: Andras Domokos Signed-off-by: Carlos Chinea Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20579 Reviewed-by: Pawel SZYSZUK Tested-by: Pawel SZYSZUK Reviewed-by: Jonas ABERG --- drivers/hsi/Kconfig | 1 + drivers/hsi/Makefile | 2 +- drivers/hsi/clients/Kconfig | 13 +++++++++++++ drivers/hsi/clients/Makefile | 5 +++++ include/linux/Kbuild | 1 + include/linux/hsi/Kbuild | 1 + 6 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 drivers/hsi/clients/Kconfig create mode 100644 drivers/hsi/clients/Makefile create mode 100644 include/linux/hsi/Kbuild (limited to 'include') diff --git a/drivers/hsi/Kconfig b/drivers/hsi/Kconfig index bba73eaf43d..2c76de438eb 100644 --- a/drivers/hsi/Kconfig +++ b/drivers/hsi/Kconfig @@ -15,5 +15,6 @@ config HSI_BOARDINFO default y source "drivers/hsi/controllers/Kconfig" +source "drivers/hsi/clients/Kconfig" endif # HSI diff --git a/drivers/hsi/Makefile b/drivers/hsi/Makefile index 0de87bd8164..d47ca5de18c 100644 --- a/drivers/hsi/Makefile +++ b/drivers/hsi/Makefile @@ -3,4 +3,4 @@ # obj-$(CONFIG_HSI_BOARDINFO) += hsi_boardinfo.o obj-$(CONFIG_HSI) += hsi.o -obj-y += controllers/ +obj-y += controllers/ clients/ diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig new file mode 100644 index 00000000000..3bacd275f47 --- /dev/null +++ b/drivers/hsi/clients/Kconfig @@ -0,0 +1,13 @@ +# +# HSI clients configuration +# + +comment "HSI clients" + +config HSI_CHAR + tristate "HSI/SSI character driver" + depends on HSI + ---help--- + 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. diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile new file mode 100644 index 00000000000..327c0e27c8b --- /dev/null +++ b/drivers/hsi/clients/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for HSI clients +# + +obj-$(CONFIG_HSI_CHAR) += hsi_char.o diff --git a/include/linux/Kbuild b/include/linux/Kbuild index c94e71781b7..fc575c034d6 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -3,6 +3,7 @@ header-y += can/ header-y += caif/ header-y += dvb/ header-y += hdlc/ +header-y += hsi/ header-y += isdn/ header-y += mmc/ header-y += nfsd/ diff --git a/include/linux/hsi/Kbuild b/include/linux/hsi/Kbuild new file mode 100644 index 00000000000..271a770b478 --- /dev/null +++ b/include/linux/hsi/Kbuild @@ -0,0 +1 @@ +header-y += hsi_char.h -- cgit v1.2.3 From 95758e6246f4cfc9cd79658b97c03c0d5ba4afc4 Mon Sep 17 00:00:00 2001 From: Derek Morton Date: Tue, 22 Nov 2011 15:09:22 +0000 Subject: u95xx: Add API for HSI channel priority Add support for specifying channel priority when configuring HSI interface. ST-Ericsson Linux next: N/A ST-Ericsson ID: 365662 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4a1a453edec6122c7d5331db43cb8237c837cc42 Signed-off-by: Derek Morton Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/42228 Reviewed-by: Pawel SZYSZUK Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: Christopher BLAIR Reviewed-by: Jonas ABERG --- include/linux/hsi/hsi.h | 2 ++ include/linux/hsi/hsi_char.h | 1 + 2 files changed, 3 insertions(+) (limited to 'include') diff --git a/include/linux/hsi/hsi.h b/include/linux/hsi/hsi.h index 1a52623a7dd..455120c1fb3 100644 --- a/include/linux/hsi/hsi.h +++ b/include/linux/hsi/hsi.h @@ -81,6 +81,8 @@ struct hsi_config { unsigned int mode; unsigned int channels; unsigned int speed; + /* ch_prio for TX only, Valid if arb_mode == HSI_ARB_PRIO */ + unsigned char ch_prio[HSI_MAX_CHANNELS]; union { unsigned int flow; /* RX only */ unsigned int arb_mode; /* TX only */ diff --git a/include/linux/hsi/hsi_char.h b/include/linux/hsi/hsi_char.h index fc498973714..4ffe33f7376 100644 --- a/include/linux/hsi/hsi_char.h +++ b/include/linux/hsi/hsi_char.h @@ -60,6 +60,7 @@ struct hsc_tx_config { uint32_t channels; uint32_t speed; uint32_t arb_mode; + uint32_t priority; }; #endif /* __HSI_CHAR_H */ -- cgit v1.2.3