android_kernel_modules_leno.../drivers/hsi/clients/hsi_flash.c

1262 lines
30 KiB
C

/*
* hsi-flash.c
*
* HSI flash device driver, implements the character device
* interface and it is used for modem FW flashing over HSI link
*
* Copyright (C) 2010 Nokia Corporation. All rights reserved.
* Copyright (C) RMC. All rights reserved.
*
*
* Contact: Andras Domokos <andras.domokos@nokia.com>
*
* 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 <linux/errno.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/ioctl.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/scatterlist.h>
#include <linux/hsi/hsi.h>
#include <linux/hsi/hsi_flash.h>
#include <linux/delay.h>
#define NEW_HSI_CORE_IF
#define HSI_FLASH_USE_DEBUG
#ifdef HSI_FLASH_USE_DEBUG
# define DPRINTK(...) printk(KERN_DEBUG __VA_ARGS__)
#else
# define DPRINTK(...)
#endif
#define HSI_FLASH_CHANNELS 8
#define HSI_FLASH_DEVS 8
#define HSI_FLASH_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_FLASH_POLL_RST (-1)
#define HSI_FLASH_POLL_OFF 0
#define HSI_FLASH_POLL_ON 1
#define HSI_FLASH_RX 0
#define HSI_FLASH_TX 1
/* LCH START */
#define POLLIN_MESSAGE_LENGTH 4
/* LCH STOP */
struct hiocgwake {
unsigned wake_state;
unsigned wake_edges;
};
struct hsi_flash_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;
struct list_head pollin_msgs_queue;
int poll_event;
struct hiocgwake wake_event;
unsigned int break_event;
spinlock_t lock;
struct fasync_struct *async_queue;
wait_queue_head_t rx_wait;
wait_queue_head_t tx_wait;
};
struct hsi_flash_client_data {
atomic_t refcnt;
int attached;
struct hsi_msg *break_msg_receive_before_setup;
struct hsi_flash_channel channels[HSI_FLASH_DEVS];
};
static unsigned int max_data_size = 65536;
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_FLASH_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_flash_dev;
static struct hsi_flash_client_data hsi_flash_cl_data;
static int hsi_flash_rx_poll(struct hsi_flash_channel *channel);
static int hsi_flash_break_request(struct hsi_client *cl);
static inline void hsi_flash_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_flash_msgs_free(struct hsi_flash_channel *channel)
{
struct hsi_msg *msg, *tmp;
list_for_each_entry_safe(msg, tmp, &channel->free_msgs_list, link) {
list_del(&msg->link);
hsi_flash_msg_free(msg);
}
list_for_each_entry_safe(msg, tmp, &channel->rx_msgs_queue, link) {
list_del(&msg->link);
hsi_flash_msg_free(msg);
}
list_for_each_entry_safe(msg, tmp, &channel->tx_msgs_queue, link) {
list_del(&msg->link);
hsi_flash_msg_free(msg);
}
list_for_each_entry_safe(msg, tmp, &channel->pollin_msgs_queue, link) {
list_del(&msg->link);
hsi_flash_msg_free(msg);
}
}
static inline struct hsi_msg *hsi_flash_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_flash_msgs_alloc(struct hsi_flash_channel *channel)
{
struct hsi_msg *msg;
int i;
for (i = 0; i < HSI_FLASH_MSGS; i++) {
msg = hsi_flash_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_flash_msgs_free(channel);
return -ENOMEM;
}
static int _hsi_flash_release(struct hsi_flash_channel *channel, int remove)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(channel->cl);
int ret = 0, refcnt;
DPRINTK("_hsi_flash_release\n");
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_FLASH_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) {
DPRINTK("hsi_flush 1\n");
hsi_flush(channel->cl);
hsi_release_port(channel->cl);
cl_data->attached = 0;
}
hsi_flash_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 void hsi_flash_start_rx(struct hsi_client *cl)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(cl);
struct hsi_flash_channel *channel = cl_data->channels;
int i;
DPRINTK("hsi_flash_start_rx\n");
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
continue;
channel->wake_event.wake_state = 1;
channel->wake_event.wake_edges++;
channel->poll_event |= POLLHUP;
wake_up_interruptible(&channel->rx_wait);
}
}
static void hsi_flash_stop_rx(struct hsi_client *cl)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(cl);
struct hsi_flash_channel *channel = cl_data->channels;
int i;
DPRINTK("hsi_flash_stop_rx\n");
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
continue;
channel->wake_event.wake_state = 0;
channel->wake_event.wake_edges++;
channel->poll_event |= POLLHUP;
wake_up_interruptible(&channel->rx_wait);
}
}
#ifdef NEW_HSI_CORE_IF
static void hsi_flash_start_stop_rx(struct hsi_client *cl, unsigned long state)
{
if (state == HSI_EVENT_START_RX)
hsi_flash_start_rx(cl);
else
hsi_flash_stop_rx(cl);
}
#endif /* NEW_HSI_CORE_IF */
static int hsi_flash_probe(struct device *dev)
{
struct hsi_flash_client_data *cl_data = &hsi_flash_cl_data;
struct hsi_flash_channel *channel = cl_data->channels;
struct hsi_client *cl = to_hsi_client(dev);
int i;
DPRINTK("hsi_flash_probe\n");
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if (channel->state == HSI_CHST_AVAIL)
channel->cl = cl;
}
atomic_set(&cl_data->refcnt, 0);
cl_data->attached = 0;
cl_data->break_msg_receive_before_setup = NULL;
hsi_client_set_drvdata(cl, cl_data);
return 0;
}
static int hsi_flash_remove(struct device *dev)
{
struct hsi_client *cl = to_hsi_client(dev);
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(cl);
struct hsi_flash_channel *channel = cl_data->channels;
int i;
#ifdef NEW_HSI_CORE_IF
hsi_unregister_port_event(channel->cl);
#else
cl->hsi_start_rx = NULL;
cl->hsi_stop_rx = NULL;
#endif /* NEW_HSI_CORE_IF */
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if (!(channel->state & HSI_CHST_AVAIL))
continue;
_hsi_flash_release(channel, 1);
}
return 0;
}
static inline unsigned int hsi_flash_msg_len_get(struct hsi_msg *msg)
{
return msg->sgt.sgl->length;
}
static inline void hsi_flash_msg_len_set(struct hsi_msg *msg, unsigned int len)
{
msg->sgt.sgl->length = len;
}
static void hsi_flash_data_available(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_channel *channel = cl_data->channels + msg->channel;
int ret;
DPRINTK("hsi_flash_data_available\n");
if (msg->status == HSI_STATUS_ERROR) {
ret = hsi_async_read(channel->cl, msg);
if (ret < 0) {
spin_lock_bh(&channel->lock);
list_add_tail(&msg->link, &channel->free_msgs_list);
channel->rxpoll = HSI_FLASH_POLL_OFF;
spin_unlock_bh(&channel->lock);
}
} else {
spin_lock_bh(&channel->lock);
channel->poll_event |= (POLLIN | POLLRDNORM);
/* LCH START */
list_add_tail(&msg->link, &channel->pollin_msgs_queue);
/*list_add_tail(&msg->link, &channel->free_msgs_list);*/
/* LCH STOP */
spin_unlock_bh(&channel->lock);
wake_up_interruptible(&channel->rx_wait);
}
}
static void hsi_flash_rx_completed(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_channel *channel = cl_data->channels + msg->channel;
DPRINTK("hsi_flash_rx_completed\n");
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_flash_rx_msg_destructor(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_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_flash_rx_poll_destructor(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_channel *channel = cl_data->channels + msg->channel;
spin_lock_bh(&channel->lock);
list_add_tail(&msg->link, &channel->free_msgs_list);
channel->rxpoll = HSI_FLASH_POLL_RST;
spin_unlock_bh(&channel->lock);
}
static int hsi_flash_rx_poll(struct hsi_flash_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_FLASH_POLL_ON)
goto out;
msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link);
list_del(&msg->link);
channel->rxpoll = HSI_FLASH_POLL_ON;
spin_unlock_bh(&channel->lock);
/*LCH START*/
hsi_flash_msg_len_set(msg, POLLIN_MESSAGE_LENGTH); /*, 0);*/
/*LCH STOP*/
msg->complete = hsi_flash_data_available;
msg->destructor = hsi_flash_rx_poll_destructor;
/* don't touch msg->context! */
DPRINTK(\
"hsi_flash_rx_poll -> allocate empty message and pass it to "
"controller\n");
ret = hsi_async_read(channel->cl, msg);
spin_lock_bh(&channel->lock);
if (ret < 0) {
DPRINTK("hsi_flash_rx_poll hsi_async_read - ERROR\n");
list_add_tail(&msg->link, &channel->free_msgs_list);
channel->rxpoll = HSI_FLASH_POLL_OFF;
goto out;
}
out:
spin_unlock_bh(&channel->lock);
return ret;
}
static void hsi_flash_tx_completed(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_channel *channel = cl_data->channels + msg->channel;
/*DPRINTK("hsi_flash_tx_completed\n"); */
/*hsi_stop_tx(channel->cl);*/
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_flash_tx_msg_destructor(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
struct hsi_flash_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_flash_rx_poll_rst(struct hsi_client *cl)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(cl);
struct hsi_flash_channel *channel = cl_data->channels;
int i;
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if ((HSI_CHST_OC(channel) == HSI_CHST_OPENED) &&
(channel->rxpoll == HSI_FLASH_POLL_RST))
hsi_flash_rx_poll(channel);
}
}
static void hsi_flash_reset(struct hsi_client *cl)
{
DPRINTK("hsi_flush 2\n");
hsi_flush(cl);
hsi_flash_rx_poll_rst(cl);
}
static void hsi_flash_rx_cancel(struct hsi_flash_channel *channel)
{
DPRINTK("hsi_flush 3\n");
hsi_flush(channel->cl);
hsi_flash_rx_poll_rst(channel->cl);
}
static void hsi_flash_tx_cancel(struct hsi_flash_channel *channel)
{
DPRINTK("hsi_flush 4\n");
hsi_flush(channel->cl);
hsi_flash_rx_poll_rst(channel->cl);
}
static void hsi_flash_bcast_break(struct hsi_client *cl)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(cl);
struct hsi_flash_channel *channel = cl_data->channels;
int i;
for (i = 0; i < HSI_FLASH_DEVS; i++, channel++) {
if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
continue;
channel->break_event++;
channel->poll_event |= POLLPRI;
DPRINTK("hsi_flash_break_received => POLLPRI\n");
wake_up_interruptible(&channel->rx_wait);
wake_up_interruptible(&channel->tx_wait);
}
}
static void hsi_flash_break_received(struct hsi_msg *msg)
{
struct hsi_flash_client_data *cl_data = hsi_client_drvdata(msg->cl);
DPRINTK("hsi_flash_break_received\n");
if (!cl_data->attached) {
/* Wait setup completion */
cl_data->break_msg_receive_before_setup = msg;
DPRINTK(\
"hsi_flash_break_received - save msg as setup is not ended\n");
return;
}
hsi_flash_bcast_break(msg->cl);
msg->destructor(msg);
}
static void hsi_flash_break_req_destructor(struct hsi_msg *msg)
{
hsi_free_msg(msg);
}
static int hsi_flash_break_request(struct hsi_client *cl)
{
struct hsi_msg *msg;
int ret = 0;
DPRINTK("hsi_flash_break_request\n");
msg = hsi_alloc_msg(0, GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->break_frame = 1;
msg->complete = hsi_flash_break_received;
msg->destructor = hsi_flash_break_req_destructor;
ret = hsi_async_read(cl, msg);
if (ret < 0)
hsi_free_msg(msg);
return ret;
}
static int hsi_flash_break_send(struct hsi_client *cl)
{
struct hsi_msg *msg;
int ret = 0;
msg = hsi_alloc_msg(0, GFP_ATOMIC);
if (!msg)
return -ENOMEM;
/*ret = hsi_start_tx(cl);
if (ret < 0) {
hsi_stop_tx(cl);
hsi_free_msg(msg);
return ret;
}*/
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;
}
return ret;
}
static ssize_t hsi_flash_read(struct file *file, char __user *buf,
size_t len, loff_t *ppos)
{
struct hsi_flash_channel *channel = file->private_data;
struct hsi_msg *msg = NULL;
ssize_t ret;
DPRINTK("hsi_flash_read\n");
if (len == 0)
return 0;
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;
}
/*LCH START*/
/* Managing here the polling case as HSI core
doesn't support this mode*/
if ((!list_empty(&channel->pollin_msgs_queue)) && \
(POLLIN_MESSAGE_LENGTH == len)) {
msg = list_first_entry(&channel->pollin_msgs_queue,
struct hsi_msg, link);
HSI_CHST_RD_SET(channel, HSI_CHST_READOFF);
channel->poll_event &= ~(POLLIN | POLLRDNORM);
spin_unlock_bh(&channel->lock);
DPRINTK("###hsi_flash_read _ POLLIN part : 0x%08X\n",
*(u32 *)msg->context);
ret = copy_to_user((void __user *)buf,
msg->context,
hsi_flash_msg_len_get(msg));
spin_lock_bh(&channel->lock);
if (ret)
ret = -EFAULT;
else
ret = hsi_flash_msg_len_get(msg);
list_del(&msg->link);
channel->rxpoll = HSI_FLASH_POLL_OFF;
goto out;
}
/*LCH STOP*/
msg = list_first_entry(&channel->free_msgs_list, struct hsi_msg, link);
list_del(&msg->link);
spin_unlock_bh(&channel->lock);
hsi_flash_msg_len_set(msg, len);
msg->complete = hsi_flash_rx_completed;
msg->destructor = hsi_flash_rx_msg_destructor;
ret = hsi_async_read(channel->cl, msg);
spin_lock_bh(&channel->lock);
channel->rxpoll = HSI_FLASH_POLL_OFF;
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_flash_msg_len_get(msg));
DPRINTK("###hsi_flash_read : 0x%08X\n",
*(u32 *)msg->context);
if (ret)
ret = -EFAULT;
else
ret = hsi_flash_msg_len_get(msg);
}
spin_lock_bh(&channel->lock);
break;
} else if (signal_pending(current)) {
spin_unlock_bh(&channel->lock);
hsi_flash_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_CLOSED)) {
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_flash_write(struct file *file, const char __user *buf,
size_t len, loff_t *ppos)
{
struct hsi_flash_channel *channel = file->private_data;
struct hsi_msg *msg = NULL;
ssize_t ret;
if ((len == 0) || !IS_ALIGNED(len, sizeof(u32))) {
DPRINTK("###hsi_flash_write fail 1\n");
return -EINVAL;
}
if (len > max_data_size)
len = max_data_size;
spin_lock_bh(&channel->lock);
if (HSI_CHST_OC(channel) != HSI_CHST_OPENED) {
DPRINTK("###hsi_flash_write fail 2\n");
ret = -ENODEV;
goto out;
}
if (HSI_CHST_WR(channel) != HSI_CHST_WRITEOFF) {
DPRINTK("###hsi_flash_write fail 3\n");
ret = -EBUSY;
goto out;
}
if (channel->ch >= channel->cl->tx_cfg.channels) {
DPRINTK("###hsi_flash_write fail 4\n");
ret = -ENODEV;
goto out;
}
if (list_empty(&channel->free_msgs_list)) {
DPRINTK("###hsi_flash_write fail 5\n");
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);
DPRINTK("###hsi_flash_write fail 6\n");
ret = -EFAULT;
goto out;
}
/*DPRINTK("###hsi_flash_write : 0x%08X\n", *(u32 *) msg->context);*/
/*
ret = hsi_start_tx(channel->cl);
spin_lock_bh(&channel->lock);
if (ret < 0) {
hsi_stop_tx(channel->cl);
spin_lock_bh(&channel->lock);
channel->poll_event |= (POLLOUT | POLLWRNORM);
HSI_CHST_WR_SET(channel, HSI_CHST_WRITEOFF);
goto out;
}
*/
hsi_flash_msg_len_set(msg, len);
msg->complete = hsi_flash_tx_completed;
msg->destructor = hsi_flash_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_flash_msg_len_get(msg);
break;
} else if (signal_pending(current)) {
DPRINTK("signal_pending !!!\n");
spin_unlock_bh(&channel->lock);
hsi_flash_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_CLOSED)) {
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_flash_poll(struct file *file, poll_table *wait)
{
struct hsi_flash_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_flash_rx_poll(channel);
return ret;
}
static long hsi_flash_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct hsi_flash_channel *channel = file->private_data;
struct hsi_flash_client_data *cl_data = &hsi_flash_cl_data;
unsigned int speed;
int ret = 0;
DPRINTK("hsi_flash_ioctl - cmd %d\n", cmd);
if (HSI_CHST_OC(channel) != HSI_CHST_OPENED)
return -ENODEV;
switch (cmd) {
case HIOCRESET:
channel->rxpoll = HSI_FLASH_POLL_OFF;
hsi_flash_reset(channel->cl);
channel->break_event = 0;
channel->wake_event.wake_edges = 0;
hsi_flash_break_request(channel->cl);
break;
case HIOCSNDBRK:
DPRINTK("send break\n");
return hsi_flash_break_send(channel->cl);
case HIOCSPEED:
if (copy_from_user(&speed, (void __user *)arg,
sizeof(unsigned int)))
return -EFAULT;
DPRINTK("change speed: %d Khz\n", speed);
channel->cl->tx_cfg.speed = speed;
ret = hsi_setup(channel->cl);
DPRINTK("hsi_setup returns %d\n", ret);
break;
case HIOCGWAKE:
DPRINTK("Get WAKE event: wake_state %d, wake_edges %d\n",
channel->wake_event.wake_state,
channel->wake_event.wake_edges);
channel->poll_event &= ~POLLHUP;
if (copy_to_user((void __user *)arg, &channel->wake_event,
sizeof(struct hiocgwake)))
return -EFAULT;
channel->wake_event.wake_edges = 0;
break;
case HIOCGBRK:
DPRINTK("Get BREAK event: break:%d\n", channel->break_event);
channel->poll_event &= ~POLLPRI;
if (copy_to_user((void __user *)arg, &channel->break_event,
sizeof(unsigned)))
return -EFAULT;
channel->break_event = 0;
break;
case HIOCTXCTRL:
/* Set WAKE LINE ON */
hsi_start_tx(channel->cl);
if (cl_data->break_msg_receive_before_setup) {
DPRINTK(\
"Setup is done, treat break msg reception now\n");
hsi_flash_break_received(
cl_data->break_msg_receive_before_setup);
cl_data->break_msg_receive_before_setup = NULL;
}
break;
default:
return -ENOIOCTLCMD;
}
return ret;
}
static int hsi_flash_open(struct inode *inode, struct file *file)
{
struct hsi_flash_client_data *cl_data = &hsi_flash_cl_data;
struct hsi_flash_channel *channel;
int ret = 0, refcnt, minor = iminor(inode);
DPRINTK("hsi_flash_open\n");
if (minor >= HSI_FLASH_DEVS) {
pr_err("Invalid node id (%d)\n", minor);
return -EINVAL;
}
channel = cl_data->channels + minor;
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);
channel->break_event = 0;
channel->wake_event.wake_state = 0;
channel->wake_event.wake_edges = 0;
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;
}
#ifdef NEW_HSI_CORE_IF
hsi_register_port_event(channel->cl, hsi_flash_start_stop_rx) ;
#else
channel->cl->hsi_start_rx = hsi_flash_start_rx;
channel->cl->hsi_stop_rx = hsi_flash_stop_rx;
#endif /* NEW_HSI_CORE_IF */
ret = hsi_setup(channel->cl);
DPRINTK("hsi_setup returns %d\n", ret);
/* Prepare msg reception for break */
ret = hsi_flash_break_request(channel->cl);
DPRINTK("hsi_flash_break_request returns %d\n", ret);
} 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_flash_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_FLASH_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_flash_release(struct inode *inode, struct file *file)
{
struct hsi_flash_channel *channel = file->private_data;
DPRINTK("hsi_flash_release\n");
/* Set WAKE LINE OFF */
hsi_stop_tx(channel->cl);
#ifdef NEW_HSI_CORE_IF
hsi_unregister_port_event(channel->cl);
#else
channel->cl->hsi_start_rx = NULL;
channel->cl->hsi_stop_rx = NULL;
#endif /* NEW_HSI_CORE_IF */
return _hsi_flash_release(channel, 0);
}
static int hsi_flash_fasync(int fd, struct file *file, int on)
{
struct hsi_flash_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_flash_fops = {
.owner = THIS_MODULE,
.read = hsi_flash_read,
.write = hsi_flash_write,
.poll = hsi_flash_poll,
.unlocked_ioctl = hsi_flash_ioctl,
.open = hsi_flash_open,
.release = hsi_flash_release,
.fasync = hsi_flash_fasync,
};
static struct hsi_client_driver hsi_flash_driver = {
.driver = {
.name = "hsi_flash",
.owner = THIS_MODULE,
.probe = hsi_flash_probe,
.remove = hsi_flash_remove,
},
};
static inline void hsi_flash_channel_init(struct hsi_flash_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);
INIT_LIST_HEAD(&channel->pollin_msgs_queue);
}
static struct cdev hsi_flash_cdev;
static int __init hsi_flash_init(void)
{
char devname[] = "hsi_flash";
struct hsi_flash_client_data *cl_data = &hsi_flash_cl_data;
struct hsi_flash_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_FLASH_DEVS && channels_map[i] >= 0; i++) {
if (channels_map[i] >= HSI_FLASH_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_FLASH_DEVS; i++, channel++) {
channel->ch = i;
channel->state = HSI_CHST_UNAVAIL;
if (test_bit(i, &ch_mask))
hsi_flash_channel_init(channel);
}
ret = hsi_register_client_driver(&hsi_flash_driver);
if (ret) {
pr_err("Error while registering HSI/SSI driver %d", ret);
return ret;
}
ret = alloc_chrdev_region(&hsi_flash_dev, 0, HSI_FLASH_DEVS, devname);
if (ret < 0) {
hsi_unregister_client_driver(&hsi_flash_driver);
return ret;
}
cdev_init(&hsi_flash_cdev, &hsi_flash_fops);
ret = cdev_add(&hsi_flash_cdev, hsi_flash_dev, HSI_FLASH_DEVS);
if (ret) {
unregister_chrdev_region(hsi_flash_dev, HSI_FLASH_DEVS);
hsi_unregister_client_driver(&hsi_flash_driver);
return ret;
}
pr_info("HSI flash device loaded\n");
return 0;
}
module_init(hsi_flash_init);
static void __exit hsi_flash_exit(void)
{
cdev_del(&hsi_flash_cdev);
unregister_chrdev_region(hsi_flash_dev, HSI_FLASH_DEVS);
hsi_unregister_client_driver(&hsi_flash_driver);
pr_info("HSI flash device removed\n");
}
module_exit(hsi_flash_exit);
MODULE_AUTHOR("RMC");
MODULE_ALIAS("hsi:hsi_flash");
MODULE_DESCRIPTION("HSI flash device");
MODULE_LICENSE("GPL v2");