162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HSI character device driver, implements the character device
462306a36Sopenharmony_ci * interface.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2010 Nokia Corporation. All rights reserved.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Contact: Andras Domokos <andras.domokos@nokia.com>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/types.h>
1362306a36Sopenharmony_ci#include <linux/atomic.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/mutex.h>
1862306a36Sopenharmony_ci#include <linux/list.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci#include <linux/kmemleak.h>
2162306a36Sopenharmony_ci#include <linux/ioctl.h>
2262306a36Sopenharmony_ci#include <linux/wait.h>
2362306a36Sopenharmony_ci#include <linux/fs.h>
2462306a36Sopenharmony_ci#include <linux/sched.h>
2562306a36Sopenharmony_ci#include <linux/device.h>
2662306a36Sopenharmony_ci#include <linux/cdev.h>
2762306a36Sopenharmony_ci#include <linux/uaccess.h>
2862306a36Sopenharmony_ci#include <linux/scatterlist.h>
2962306a36Sopenharmony_ci#include <linux/stat.h>
3062306a36Sopenharmony_ci#include <linux/hsi/hsi.h>
3162306a36Sopenharmony_ci#include <linux/hsi/hsi_char.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define HSC_DEVS		16 /* Num of channels */
3462306a36Sopenharmony_ci#define HSC_MSGS		4
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define HSC_RXBREAK		0
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define HSC_ID_BITS		6
3962306a36Sopenharmony_ci#define HSC_PORT_ID_BITS	4
4062306a36Sopenharmony_ci#define HSC_ID_MASK		3
4162306a36Sopenharmony_ci#define HSC_PORT_ID_MASK	3
4262306a36Sopenharmony_ci#define HSC_CH_MASK		0xf
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/*
4562306a36Sopenharmony_ci * We support up to 4 controllers that can have up to 4
4662306a36Sopenharmony_ci * ports, which should currently be more than enough.
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_ci#define HSC_BASEMINOR(id, port_id) \
4962306a36Sopenharmony_ci		((((id) & HSC_ID_MASK) << HSC_ID_BITS) | \
5062306a36Sopenharmony_ci		(((port_id) & HSC_PORT_ID_MASK) << HSC_PORT_ID_BITS))
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cienum {
5362306a36Sopenharmony_ci	HSC_CH_OPEN,
5462306a36Sopenharmony_ci	HSC_CH_READ,
5562306a36Sopenharmony_ci	HSC_CH_WRITE,
5662306a36Sopenharmony_ci	HSC_CH_WLINE,
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cienum {
6062306a36Sopenharmony_ci	HSC_RX,
6162306a36Sopenharmony_ci	HSC_TX,
6262306a36Sopenharmony_ci};
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistruct hsc_client_data;
6562306a36Sopenharmony_ci/**
6662306a36Sopenharmony_ci * struct hsc_channel - hsi_char internal channel data
6762306a36Sopenharmony_ci * @ch: channel number
6862306a36Sopenharmony_ci * @flags: Keeps state of the channel (open/close, reading, writing)
6962306a36Sopenharmony_ci * @free_msgs_list: List of free HSI messages/requests
7062306a36Sopenharmony_ci * @rx_msgs_queue: List of pending RX requests
7162306a36Sopenharmony_ci * @tx_msgs_queue: List of pending TX requests
7262306a36Sopenharmony_ci * @lock: Serialize access to the lists
7362306a36Sopenharmony_ci * @cl: reference to the associated hsi_client
7462306a36Sopenharmony_ci * @cl_data: reference to the client data that this channels belongs to
7562306a36Sopenharmony_ci * @rx_wait: RX requests wait queue
7662306a36Sopenharmony_ci * @tx_wait: TX requests wait queue
7762306a36Sopenharmony_ci */
7862306a36Sopenharmony_cistruct hsc_channel {
7962306a36Sopenharmony_ci	unsigned int		ch;
8062306a36Sopenharmony_ci	unsigned long		flags;
8162306a36Sopenharmony_ci	struct list_head	free_msgs_list;
8262306a36Sopenharmony_ci	struct list_head	rx_msgs_queue;
8362306a36Sopenharmony_ci	struct list_head	tx_msgs_queue;
8462306a36Sopenharmony_ci	spinlock_t		lock;
8562306a36Sopenharmony_ci	struct hsi_client	*cl;
8662306a36Sopenharmony_ci	struct hsc_client_data *cl_data;
8762306a36Sopenharmony_ci	wait_queue_head_t	rx_wait;
8862306a36Sopenharmony_ci	wait_queue_head_t	tx_wait;
8962306a36Sopenharmony_ci};
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/**
9262306a36Sopenharmony_ci * struct hsc_client_data - hsi_char internal client data
9362306a36Sopenharmony_ci * @cdev: Characther device associated to the hsi_client
9462306a36Sopenharmony_ci * @lock: Lock to serialize open/close access
9562306a36Sopenharmony_ci * @flags: Keeps track of port state (rx hwbreak armed)
9662306a36Sopenharmony_ci * @usecnt: Use count for claiming the HSI port (mutex protected)
9762306a36Sopenharmony_ci * @cl: Referece to the HSI client
9862306a36Sopenharmony_ci * @channels: Array of channels accessible by the client
9962306a36Sopenharmony_ci */
10062306a36Sopenharmony_cistruct hsc_client_data {
10162306a36Sopenharmony_ci	struct cdev		cdev;
10262306a36Sopenharmony_ci	struct mutex		lock;
10362306a36Sopenharmony_ci	unsigned long		flags;
10462306a36Sopenharmony_ci	unsigned int		usecnt;
10562306a36Sopenharmony_ci	struct hsi_client	*cl;
10662306a36Sopenharmony_ci	struct hsc_channel	channels[HSC_DEVS];
10762306a36Sopenharmony_ci};
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/* Stores the major number dynamically allocated for hsi_char */
11062306a36Sopenharmony_cistatic unsigned int hsc_major;
11162306a36Sopenharmony_ci/* Maximum buffer size that hsi_char will accept from userspace */
11262306a36Sopenharmony_cistatic unsigned int max_data_size = 0x1000;
11362306a36Sopenharmony_cimodule_param(max_data_size, uint, 0);
11462306a36Sopenharmony_ciMODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)");
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg,
11762306a36Sopenharmony_ci							struct list_head *queue)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	unsigned long flags;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
12262306a36Sopenharmony_ci	list_add_tail(&msg->link, queue);
12362306a36Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic struct hsi_msg *hsc_get_first_msg(struct hsc_channel *channel,
12762306a36Sopenharmony_ci							struct list_head *queue)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	struct hsi_msg *msg = NULL;
13062306a36Sopenharmony_ci	unsigned long flags;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (list_empty(queue))
13562306a36Sopenharmony_ci		goto out;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	msg = list_first_entry(queue, struct hsi_msg, link);
13862306a36Sopenharmony_ci	list_del(&msg->link);
13962306a36Sopenharmony_ciout:
14062306a36Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return msg;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic inline void hsc_msg_free(struct hsi_msg *msg)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	kfree(sg_virt(msg->sgt.sgl));
14862306a36Sopenharmony_ci	hsi_free_msg(msg);
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic void hsc_free_list(struct list_head *list)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct hsi_msg *msg, *tmp;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	list_for_each_entry_safe(msg, tmp, list, link) {
15662306a36Sopenharmony_ci		list_del(&msg->link);
15762306a36Sopenharmony_ci		hsc_msg_free(msg);
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void hsc_reset_list(struct hsc_channel *channel, struct list_head *l)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	unsigned long flags;
16462306a36Sopenharmony_ci	LIST_HEAD(list);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
16762306a36Sopenharmony_ci	list_splice_init(l, &list);
16862306a36Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	hsc_free_list(&list);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic inline struct hsi_msg *hsc_msg_alloc(unsigned int alloc_size)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct hsi_msg *msg;
17662306a36Sopenharmony_ci	void *buf;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	msg = hsi_alloc_msg(1, GFP_KERNEL);
17962306a36Sopenharmony_ci	if (!msg)
18062306a36Sopenharmony_ci		goto out;
18162306a36Sopenharmony_ci	buf = kmalloc(alloc_size, GFP_KERNEL);
18262306a36Sopenharmony_ci	if (!buf) {
18362306a36Sopenharmony_ci		hsi_free_msg(msg);
18462306a36Sopenharmony_ci		goto out;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci	sg_init_one(msg->sgt.sgl, buf, alloc_size);
18762306a36Sopenharmony_ci	/* Ignore false positive, due to sg pointer handling */
18862306a36Sopenharmony_ci	kmemleak_ignore(buf);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	return msg;
19162306a36Sopenharmony_ciout:
19262306a36Sopenharmony_ci	return NULL;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic inline int hsc_msgs_alloc(struct hsc_channel *channel)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	struct hsi_msg *msg;
19862306a36Sopenharmony_ci	int i;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	for (i = 0; i < HSC_MSGS; i++) {
20162306a36Sopenharmony_ci		msg = hsc_msg_alloc(max_data_size);
20262306a36Sopenharmony_ci		if (!msg)
20362306a36Sopenharmony_ci			goto out;
20462306a36Sopenharmony_ci		msg->channel = channel->ch;
20562306a36Sopenharmony_ci		list_add_tail(&msg->link, &channel->free_msgs_list);
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return 0;
20962306a36Sopenharmony_ciout:
21062306a36Sopenharmony_ci	hsc_free_list(&channel->free_msgs_list);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	return -ENOMEM;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic inline unsigned int hsc_msg_len_get(struct hsi_msg *msg)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	return msg->sgt.sgl->length;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic inline void hsc_msg_len_set(struct hsi_msg *msg, unsigned int len)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	msg->sgt.sgl->length = len;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic void hsc_rx_completed(struct hsi_msg *msg)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
22862306a36Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels + msg->channel;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	if (test_bit(HSC_CH_READ, &channel->flags)) {
23162306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->rx_msgs_queue);
23262306a36Sopenharmony_ci		wake_up(&channel->rx_wait);
23362306a36Sopenharmony_ci	} else {
23462306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic void hsc_rx_msg_destructor(struct hsi_msg *msg)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	msg->status = HSI_STATUS_ERROR;
24162306a36Sopenharmony_ci	hsc_msg_len_set(msg, 0);
24262306a36Sopenharmony_ci	hsc_rx_completed(msg);
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic void hsc_tx_completed(struct hsi_msg *msg)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
24862306a36Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels + msg->channel;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (test_bit(HSC_CH_WRITE, &channel->flags)) {
25162306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->tx_msgs_queue);
25262306a36Sopenharmony_ci		wake_up(&channel->tx_wait);
25362306a36Sopenharmony_ci	} else {
25462306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic void hsc_tx_msg_destructor(struct hsi_msg *msg)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	msg->status = HSI_STATUS_ERROR;
26162306a36Sopenharmony_ci	hsc_msg_len_set(msg, 0);
26262306a36Sopenharmony_ci	hsc_tx_completed(msg);
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic void hsc_break_req_destructor(struct hsi_msg *msg)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	hsi_free_msg(msg);
27062306a36Sopenharmony_ci	clear_bit(HSC_RXBREAK, &cl_data->flags);
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic void hsc_break_received(struct hsi_msg *msg)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
27662306a36Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels;
27762306a36Sopenharmony_ci	int i, ret;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* Broadcast HWBREAK on all channels */
28062306a36Sopenharmony_ci	for (i = 0; i < HSC_DEVS; i++, channel++) {
28162306a36Sopenharmony_ci		struct hsi_msg *msg2;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci		if (!test_bit(HSC_CH_READ, &channel->flags))
28462306a36Sopenharmony_ci			continue;
28562306a36Sopenharmony_ci		msg2 = hsc_get_first_msg(channel, &channel->free_msgs_list);
28662306a36Sopenharmony_ci		if (!msg2)
28762306a36Sopenharmony_ci			continue;
28862306a36Sopenharmony_ci		clear_bit(HSC_CH_READ, &channel->flags);
28962306a36Sopenharmony_ci		hsc_msg_len_set(msg2, 0);
29062306a36Sopenharmony_ci		msg2->status = HSI_STATUS_COMPLETED;
29162306a36Sopenharmony_ci		hsc_add_tail(channel, msg2, &channel->rx_msgs_queue);
29262306a36Sopenharmony_ci		wake_up(&channel->rx_wait);
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci	hsi_flush(msg->cl);
29562306a36Sopenharmony_ci	ret = hsi_async_read(msg->cl, msg);
29662306a36Sopenharmony_ci	if (ret < 0)
29762306a36Sopenharmony_ci		hsc_break_req_destructor(msg);
29862306a36Sopenharmony_ci}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cistatic int hsc_break_request(struct hsi_client *cl)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(cl);
30362306a36Sopenharmony_ci	struct hsi_msg *msg;
30462306a36Sopenharmony_ci	int ret;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (test_and_set_bit(HSC_RXBREAK, &cl_data->flags))
30762306a36Sopenharmony_ci		return -EBUSY;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	msg = hsi_alloc_msg(0, GFP_KERNEL);
31062306a36Sopenharmony_ci	if (!msg) {
31162306a36Sopenharmony_ci		clear_bit(HSC_RXBREAK, &cl_data->flags);
31262306a36Sopenharmony_ci		return -ENOMEM;
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci	msg->break_frame = 1;
31562306a36Sopenharmony_ci	msg->complete = hsc_break_received;
31662306a36Sopenharmony_ci	msg->destructor = hsc_break_req_destructor;
31762306a36Sopenharmony_ci	ret = hsi_async_read(cl, msg);
31862306a36Sopenharmony_ci	if (ret < 0)
31962306a36Sopenharmony_ci		hsc_break_req_destructor(msg);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	return ret;
32262306a36Sopenharmony_ci}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_cistatic int hsc_break_send(struct hsi_client *cl)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct hsi_msg *msg;
32762306a36Sopenharmony_ci	int ret;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	msg = hsi_alloc_msg(0, GFP_ATOMIC);
33062306a36Sopenharmony_ci	if (!msg)
33162306a36Sopenharmony_ci		return -ENOMEM;
33262306a36Sopenharmony_ci	msg->break_frame = 1;
33362306a36Sopenharmony_ci	msg->complete = hsi_free_msg;
33462306a36Sopenharmony_ci	msg->destructor = hsi_free_msg;
33562306a36Sopenharmony_ci	ret = hsi_async_write(cl, msg);
33662306a36Sopenharmony_ci	if (ret < 0)
33762306a36Sopenharmony_ci		hsi_free_msg(msg);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	return ret;
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_cistatic int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct hsi_config tmp;
34562306a36Sopenharmony_ci	int ret;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	if ((rxc->mode != HSI_MODE_STREAM) && (rxc->mode != HSI_MODE_FRAME))
34862306a36Sopenharmony_ci		return -EINVAL;
34962306a36Sopenharmony_ci	if ((rxc->channels == 0) || (rxc->channels > HSC_DEVS))
35062306a36Sopenharmony_ci		return -EINVAL;
35162306a36Sopenharmony_ci	if (rxc->channels & (rxc->channels - 1))
35262306a36Sopenharmony_ci		return -EINVAL;
35362306a36Sopenharmony_ci	if ((rxc->flow != HSI_FLOW_SYNC) && (rxc->flow != HSI_FLOW_PIPE))
35462306a36Sopenharmony_ci		return -EINVAL;
35562306a36Sopenharmony_ci	tmp = cl->rx_cfg;
35662306a36Sopenharmony_ci	cl->rx_cfg.mode = rxc->mode;
35762306a36Sopenharmony_ci	cl->rx_cfg.num_hw_channels = rxc->channels;
35862306a36Sopenharmony_ci	cl->rx_cfg.flow = rxc->flow;
35962306a36Sopenharmony_ci	ret = hsi_setup(cl);
36062306a36Sopenharmony_ci	if (ret < 0) {
36162306a36Sopenharmony_ci		cl->rx_cfg = tmp;
36262306a36Sopenharmony_ci		return ret;
36362306a36Sopenharmony_ci	}
36462306a36Sopenharmony_ci	if (rxc->mode == HSI_MODE_FRAME)
36562306a36Sopenharmony_ci		hsc_break_request(cl);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	return ret;
36862306a36Sopenharmony_ci}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_cistatic inline void hsc_rx_get(struct hsi_client *cl, struct hsc_rx_config *rxc)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	rxc->mode = cl->rx_cfg.mode;
37362306a36Sopenharmony_ci	rxc->channels = cl->rx_cfg.num_hw_channels;
37462306a36Sopenharmony_ci	rxc->flow = cl->rx_cfg.flow;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	struct hsi_config tmp;
38062306a36Sopenharmony_ci	int ret;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	if ((txc->mode != HSI_MODE_STREAM) && (txc->mode != HSI_MODE_FRAME))
38362306a36Sopenharmony_ci		return -EINVAL;
38462306a36Sopenharmony_ci	if ((txc->channels == 0) || (txc->channels > HSC_DEVS))
38562306a36Sopenharmony_ci		return -EINVAL;
38662306a36Sopenharmony_ci	if (txc->channels & (txc->channels - 1))
38762306a36Sopenharmony_ci		return -EINVAL;
38862306a36Sopenharmony_ci	if ((txc->arb_mode != HSI_ARB_RR) && (txc->arb_mode != HSI_ARB_PRIO))
38962306a36Sopenharmony_ci		return -EINVAL;
39062306a36Sopenharmony_ci	tmp = cl->tx_cfg;
39162306a36Sopenharmony_ci	cl->tx_cfg.mode = txc->mode;
39262306a36Sopenharmony_ci	cl->tx_cfg.num_hw_channels = txc->channels;
39362306a36Sopenharmony_ci	cl->tx_cfg.speed = txc->speed;
39462306a36Sopenharmony_ci	cl->tx_cfg.arb_mode = txc->arb_mode;
39562306a36Sopenharmony_ci	ret = hsi_setup(cl);
39662306a36Sopenharmony_ci	if (ret < 0) {
39762306a36Sopenharmony_ci		cl->tx_cfg = tmp;
39862306a36Sopenharmony_ci		return ret;
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	return ret;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic inline void hsc_tx_get(struct hsi_client *cl, struct hsc_tx_config *txc)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	txc->mode = cl->tx_cfg.mode;
40762306a36Sopenharmony_ci	txc->channels = cl->tx_cfg.num_hw_channels;
40862306a36Sopenharmony_ci	txc->speed = cl->tx_cfg.speed;
40962306a36Sopenharmony_ci	txc->arb_mode = cl->tx_cfg.arb_mode;
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic ssize_t hsc_read(struct file *file, char __user *buf, size_t len,
41362306a36Sopenharmony_ci						loff_t *ppos __maybe_unused)
41462306a36Sopenharmony_ci{
41562306a36Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
41662306a36Sopenharmony_ci	struct hsi_msg *msg;
41762306a36Sopenharmony_ci	ssize_t ret;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	if (len == 0)
42062306a36Sopenharmony_ci		return 0;
42162306a36Sopenharmony_ci	if (!IS_ALIGNED(len, sizeof(u32)))
42262306a36Sopenharmony_ci		return -EINVAL;
42362306a36Sopenharmony_ci	if (len > max_data_size)
42462306a36Sopenharmony_ci		len = max_data_size;
42562306a36Sopenharmony_ci	if (channel->ch >= channel->cl->rx_cfg.num_hw_channels)
42662306a36Sopenharmony_ci		return -ECHRNG;
42762306a36Sopenharmony_ci	if (test_and_set_bit(HSC_CH_READ, &channel->flags))
42862306a36Sopenharmony_ci		return -EBUSY;
42962306a36Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->free_msgs_list);
43062306a36Sopenharmony_ci	if (!msg) {
43162306a36Sopenharmony_ci		ret = -ENOSPC;
43262306a36Sopenharmony_ci		goto out;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci	hsc_msg_len_set(msg, len);
43562306a36Sopenharmony_ci	msg->complete = hsc_rx_completed;
43662306a36Sopenharmony_ci	msg->destructor = hsc_rx_msg_destructor;
43762306a36Sopenharmony_ci	ret = hsi_async_read(channel->cl, msg);
43862306a36Sopenharmony_ci	if (ret < 0) {
43962306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
44062306a36Sopenharmony_ci		goto out;
44162306a36Sopenharmony_ci	}
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	ret = wait_event_interruptible(channel->rx_wait,
44462306a36Sopenharmony_ci					!list_empty(&channel->rx_msgs_queue));
44562306a36Sopenharmony_ci	if (ret < 0) {
44662306a36Sopenharmony_ci		clear_bit(HSC_CH_READ, &channel->flags);
44762306a36Sopenharmony_ci		hsi_flush(channel->cl);
44862306a36Sopenharmony_ci		return -EINTR;
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->rx_msgs_queue);
45262306a36Sopenharmony_ci	if (msg) {
45362306a36Sopenharmony_ci		if (msg->status != HSI_STATUS_ERROR) {
45462306a36Sopenharmony_ci			ret = copy_to_user((void __user *)buf,
45562306a36Sopenharmony_ci			sg_virt(msg->sgt.sgl), hsc_msg_len_get(msg));
45662306a36Sopenharmony_ci			if (ret)
45762306a36Sopenharmony_ci				ret = -EFAULT;
45862306a36Sopenharmony_ci			else
45962306a36Sopenharmony_ci				ret = hsc_msg_len_get(msg);
46062306a36Sopenharmony_ci		} else {
46162306a36Sopenharmony_ci			ret = -EIO;
46262306a36Sopenharmony_ci		}
46362306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
46462306a36Sopenharmony_ci	}
46562306a36Sopenharmony_ciout:
46662306a36Sopenharmony_ci	clear_bit(HSC_CH_READ, &channel->flags);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	return ret;
46962306a36Sopenharmony_ci}
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_cistatic ssize_t hsc_write(struct file *file, const char __user *buf, size_t len,
47262306a36Sopenharmony_ci						loff_t *ppos __maybe_unused)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
47562306a36Sopenharmony_ci	struct hsi_msg *msg;
47662306a36Sopenharmony_ci	ssize_t ret;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	if ((len == 0) || !IS_ALIGNED(len, sizeof(u32)))
47962306a36Sopenharmony_ci		return -EINVAL;
48062306a36Sopenharmony_ci	if (len > max_data_size)
48162306a36Sopenharmony_ci		len = max_data_size;
48262306a36Sopenharmony_ci	if (channel->ch >= channel->cl->tx_cfg.num_hw_channels)
48362306a36Sopenharmony_ci		return -ECHRNG;
48462306a36Sopenharmony_ci	if (test_and_set_bit(HSC_CH_WRITE, &channel->flags))
48562306a36Sopenharmony_ci		return -EBUSY;
48662306a36Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->free_msgs_list);
48762306a36Sopenharmony_ci	if (!msg) {
48862306a36Sopenharmony_ci		clear_bit(HSC_CH_WRITE, &channel->flags);
48962306a36Sopenharmony_ci		return -ENOSPC;
49062306a36Sopenharmony_ci	}
49162306a36Sopenharmony_ci	if (copy_from_user(sg_virt(msg->sgt.sgl), (void __user *)buf, len)) {
49262306a36Sopenharmony_ci		ret = -EFAULT;
49362306a36Sopenharmony_ci		goto out;
49462306a36Sopenharmony_ci	}
49562306a36Sopenharmony_ci	hsc_msg_len_set(msg, len);
49662306a36Sopenharmony_ci	msg->complete = hsc_tx_completed;
49762306a36Sopenharmony_ci	msg->destructor = hsc_tx_msg_destructor;
49862306a36Sopenharmony_ci	ret = hsi_async_write(channel->cl, msg);
49962306a36Sopenharmony_ci	if (ret < 0)
50062306a36Sopenharmony_ci		goto out;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	ret = wait_event_interruptible(channel->tx_wait,
50362306a36Sopenharmony_ci					!list_empty(&channel->tx_msgs_queue));
50462306a36Sopenharmony_ci	if (ret < 0) {
50562306a36Sopenharmony_ci		clear_bit(HSC_CH_WRITE, &channel->flags);
50662306a36Sopenharmony_ci		hsi_flush(channel->cl);
50762306a36Sopenharmony_ci		return -EINTR;
50862306a36Sopenharmony_ci	}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->tx_msgs_queue);
51162306a36Sopenharmony_ci	if (msg) {
51262306a36Sopenharmony_ci		if (msg->status == HSI_STATUS_ERROR)
51362306a36Sopenharmony_ci			ret = -EIO;
51462306a36Sopenharmony_ci		else
51562306a36Sopenharmony_ci			ret = hsc_msg_len_get(msg);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ciout:
52062306a36Sopenharmony_ci	clear_bit(HSC_CH_WRITE, &channel->flags);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	return ret;
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic long hsc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
52862306a36Sopenharmony_ci	unsigned int state;
52962306a36Sopenharmony_ci	struct hsc_rx_config rxc;
53062306a36Sopenharmony_ci	struct hsc_tx_config txc;
53162306a36Sopenharmony_ci	long ret = 0;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	switch (cmd) {
53462306a36Sopenharmony_ci	case HSC_RESET:
53562306a36Sopenharmony_ci		hsi_flush(channel->cl);
53662306a36Sopenharmony_ci		break;
53762306a36Sopenharmony_ci	case HSC_SET_PM:
53862306a36Sopenharmony_ci		if (copy_from_user(&state, (void __user *)arg, sizeof(state)))
53962306a36Sopenharmony_ci			return -EFAULT;
54062306a36Sopenharmony_ci		if (state == HSC_PM_DISABLE) {
54162306a36Sopenharmony_ci			if (test_and_set_bit(HSC_CH_WLINE, &channel->flags))
54262306a36Sopenharmony_ci				return -EINVAL;
54362306a36Sopenharmony_ci			ret = hsi_start_tx(channel->cl);
54462306a36Sopenharmony_ci		} else if (state == HSC_PM_ENABLE) {
54562306a36Sopenharmony_ci			if (!test_and_clear_bit(HSC_CH_WLINE, &channel->flags))
54662306a36Sopenharmony_ci				return -EINVAL;
54762306a36Sopenharmony_ci			ret = hsi_stop_tx(channel->cl);
54862306a36Sopenharmony_ci		} else {
54962306a36Sopenharmony_ci			ret = -EINVAL;
55062306a36Sopenharmony_ci		}
55162306a36Sopenharmony_ci		break;
55262306a36Sopenharmony_ci	case HSC_SEND_BREAK:
55362306a36Sopenharmony_ci		return hsc_break_send(channel->cl);
55462306a36Sopenharmony_ci	case HSC_SET_RX:
55562306a36Sopenharmony_ci		if (copy_from_user(&rxc, (void __user *)arg, sizeof(rxc)))
55662306a36Sopenharmony_ci			return -EFAULT;
55762306a36Sopenharmony_ci		return hsc_rx_set(channel->cl, &rxc);
55862306a36Sopenharmony_ci	case HSC_GET_RX:
55962306a36Sopenharmony_ci		hsc_rx_get(channel->cl, &rxc);
56062306a36Sopenharmony_ci		if (copy_to_user((void __user *)arg, &rxc, sizeof(rxc)))
56162306a36Sopenharmony_ci			return -EFAULT;
56262306a36Sopenharmony_ci		break;
56362306a36Sopenharmony_ci	case HSC_SET_TX:
56462306a36Sopenharmony_ci		if (copy_from_user(&txc, (void __user *)arg, sizeof(txc)))
56562306a36Sopenharmony_ci			return -EFAULT;
56662306a36Sopenharmony_ci		return hsc_tx_set(channel->cl, &txc);
56762306a36Sopenharmony_ci	case HSC_GET_TX:
56862306a36Sopenharmony_ci		hsc_tx_get(channel->cl, &txc);
56962306a36Sopenharmony_ci		if (copy_to_user((void __user *)arg, &txc, sizeof(txc)))
57062306a36Sopenharmony_ci			return -EFAULT;
57162306a36Sopenharmony_ci		break;
57262306a36Sopenharmony_ci	default:
57362306a36Sopenharmony_ci		return -ENOIOCTLCMD;
57462306a36Sopenharmony_ci	}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	return ret;
57762306a36Sopenharmony_ci}
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_cistatic inline void __hsc_port_release(struct hsc_client_data *cl_data)
58062306a36Sopenharmony_ci{
58162306a36Sopenharmony_ci	BUG_ON(cl_data->usecnt == 0);
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	if (--cl_data->usecnt == 0) {
58462306a36Sopenharmony_ci		hsi_flush(cl_data->cl);
58562306a36Sopenharmony_ci		hsi_release_port(cl_data->cl);
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_cistatic int hsc_open(struct inode *inode, struct file *file)
59062306a36Sopenharmony_ci{
59162306a36Sopenharmony_ci	struct hsc_client_data *cl_data;
59262306a36Sopenharmony_ci	struct hsc_channel *channel;
59362306a36Sopenharmony_ci	int ret = 0;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	pr_debug("open, minor = %d\n", iminor(inode));
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	cl_data = container_of(inode->i_cdev, struct hsc_client_data, cdev);
59862306a36Sopenharmony_ci	mutex_lock(&cl_data->lock);
59962306a36Sopenharmony_ci	channel = cl_data->channels + (iminor(inode) & HSC_CH_MASK);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	if (test_and_set_bit(HSC_CH_OPEN, &channel->flags)) {
60262306a36Sopenharmony_ci		ret = -EBUSY;
60362306a36Sopenharmony_ci		goto out;
60462306a36Sopenharmony_ci	}
60562306a36Sopenharmony_ci	/*
60662306a36Sopenharmony_ci	 * Check if we have already claimed the port associated to the HSI
60762306a36Sopenharmony_ci	 * client. If not then try to claim it, else increase its refcount
60862306a36Sopenharmony_ci	 */
60962306a36Sopenharmony_ci	if (cl_data->usecnt == 0) {
61062306a36Sopenharmony_ci		ret = hsi_claim_port(cl_data->cl, 0);
61162306a36Sopenharmony_ci		if (ret < 0)
61262306a36Sopenharmony_ci			goto out;
61362306a36Sopenharmony_ci		hsi_setup(cl_data->cl);
61462306a36Sopenharmony_ci	}
61562306a36Sopenharmony_ci	cl_data->usecnt++;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	ret = hsc_msgs_alloc(channel);
61862306a36Sopenharmony_ci	if (ret < 0) {
61962306a36Sopenharmony_ci		__hsc_port_release(cl_data);
62062306a36Sopenharmony_ci		goto out;
62162306a36Sopenharmony_ci	}
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	file->private_data = channel;
62462306a36Sopenharmony_ci	mutex_unlock(&cl_data->lock);
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	return ret;
62762306a36Sopenharmony_ciout:
62862306a36Sopenharmony_ci	mutex_unlock(&cl_data->lock);
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	return ret;
63162306a36Sopenharmony_ci}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_cistatic int hsc_release(struct inode *inode __maybe_unused, struct file *file)
63462306a36Sopenharmony_ci{
63562306a36Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
63662306a36Sopenharmony_ci	struct hsc_client_data *cl_data = channel->cl_data;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	mutex_lock(&cl_data->lock);
63962306a36Sopenharmony_ci	file->private_data = NULL;
64062306a36Sopenharmony_ci	if (test_and_clear_bit(HSC_CH_WLINE, &channel->flags))
64162306a36Sopenharmony_ci		hsi_stop_tx(channel->cl);
64262306a36Sopenharmony_ci	__hsc_port_release(cl_data);
64362306a36Sopenharmony_ci	hsc_reset_list(channel, &channel->rx_msgs_queue);
64462306a36Sopenharmony_ci	hsc_reset_list(channel, &channel->tx_msgs_queue);
64562306a36Sopenharmony_ci	hsc_reset_list(channel, &channel->free_msgs_list);
64662306a36Sopenharmony_ci	clear_bit(HSC_CH_READ, &channel->flags);
64762306a36Sopenharmony_ci	clear_bit(HSC_CH_WRITE, &channel->flags);
64862306a36Sopenharmony_ci	clear_bit(HSC_CH_OPEN, &channel->flags);
64962306a36Sopenharmony_ci	wake_up(&channel->rx_wait);
65062306a36Sopenharmony_ci	wake_up(&channel->tx_wait);
65162306a36Sopenharmony_ci	mutex_unlock(&cl_data->lock);
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	return 0;
65462306a36Sopenharmony_ci}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_cistatic const struct file_operations hsc_fops = {
65762306a36Sopenharmony_ci	.owner		= THIS_MODULE,
65862306a36Sopenharmony_ci	.read		= hsc_read,
65962306a36Sopenharmony_ci	.write		= hsc_write,
66062306a36Sopenharmony_ci	.unlocked_ioctl	= hsc_ioctl,
66162306a36Sopenharmony_ci	.open		= hsc_open,
66262306a36Sopenharmony_ci	.release	= hsc_release,
66362306a36Sopenharmony_ci};
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cistatic void hsc_channel_init(struct hsc_channel *channel)
66662306a36Sopenharmony_ci{
66762306a36Sopenharmony_ci	init_waitqueue_head(&channel->rx_wait);
66862306a36Sopenharmony_ci	init_waitqueue_head(&channel->tx_wait);
66962306a36Sopenharmony_ci	spin_lock_init(&channel->lock);
67062306a36Sopenharmony_ci	INIT_LIST_HEAD(&channel->free_msgs_list);
67162306a36Sopenharmony_ci	INIT_LIST_HEAD(&channel->rx_msgs_queue);
67262306a36Sopenharmony_ci	INIT_LIST_HEAD(&channel->tx_msgs_queue);
67362306a36Sopenharmony_ci}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistatic int hsc_probe(struct device *dev)
67662306a36Sopenharmony_ci{
67762306a36Sopenharmony_ci	const char devname[] = "hsi_char";
67862306a36Sopenharmony_ci	struct hsc_client_data *cl_data;
67962306a36Sopenharmony_ci	struct hsc_channel *channel;
68062306a36Sopenharmony_ci	struct hsi_client *cl = to_hsi_client(dev);
68162306a36Sopenharmony_ci	unsigned int hsc_baseminor;
68262306a36Sopenharmony_ci	dev_t hsc_dev;
68362306a36Sopenharmony_ci	int ret;
68462306a36Sopenharmony_ci	int i;
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	cl_data = kzalloc(sizeof(*cl_data), GFP_KERNEL);
68762306a36Sopenharmony_ci	if (!cl_data)
68862306a36Sopenharmony_ci		return -ENOMEM;
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci	hsc_baseminor = HSC_BASEMINOR(hsi_id(cl), hsi_port_id(cl));
69162306a36Sopenharmony_ci	if (!hsc_major) {
69262306a36Sopenharmony_ci		ret = alloc_chrdev_region(&hsc_dev, hsc_baseminor,
69362306a36Sopenharmony_ci						HSC_DEVS, devname);
69462306a36Sopenharmony_ci		if (ret == 0)
69562306a36Sopenharmony_ci			hsc_major = MAJOR(hsc_dev);
69662306a36Sopenharmony_ci	} else {
69762306a36Sopenharmony_ci		hsc_dev = MKDEV(hsc_major, hsc_baseminor);
69862306a36Sopenharmony_ci		ret = register_chrdev_region(hsc_dev, HSC_DEVS, devname);
69962306a36Sopenharmony_ci	}
70062306a36Sopenharmony_ci	if (ret < 0) {
70162306a36Sopenharmony_ci		dev_err(dev, "Device %s allocation failed %d\n",
70262306a36Sopenharmony_ci					hsc_major ? "minor" : "major", ret);
70362306a36Sopenharmony_ci		goto out1;
70462306a36Sopenharmony_ci	}
70562306a36Sopenharmony_ci	mutex_init(&cl_data->lock);
70662306a36Sopenharmony_ci	hsi_client_set_drvdata(cl, cl_data);
70762306a36Sopenharmony_ci	cdev_init(&cl_data->cdev, &hsc_fops);
70862306a36Sopenharmony_ci	cl_data->cdev.owner = THIS_MODULE;
70962306a36Sopenharmony_ci	cl_data->cl = cl;
71062306a36Sopenharmony_ci	for (i = 0, channel = cl_data->channels; i < HSC_DEVS; i++, channel++) {
71162306a36Sopenharmony_ci		hsc_channel_init(channel);
71262306a36Sopenharmony_ci		channel->ch = i;
71362306a36Sopenharmony_ci		channel->cl = cl;
71462306a36Sopenharmony_ci		channel->cl_data = cl_data;
71562306a36Sopenharmony_ci	}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	/* 1 hsi client -> N char devices (one for each channel) */
71862306a36Sopenharmony_ci	ret = cdev_add(&cl_data->cdev, hsc_dev, HSC_DEVS);
71962306a36Sopenharmony_ci	if (ret) {
72062306a36Sopenharmony_ci		dev_err(dev, "Could not add char device %d\n", ret);
72162306a36Sopenharmony_ci		goto out2;
72262306a36Sopenharmony_ci	}
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	return 0;
72562306a36Sopenharmony_ciout2:
72662306a36Sopenharmony_ci	unregister_chrdev_region(hsc_dev, HSC_DEVS);
72762306a36Sopenharmony_ciout1:
72862306a36Sopenharmony_ci	kfree(cl_data);
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	return ret;
73162306a36Sopenharmony_ci}
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_cistatic int hsc_remove(struct device *dev)
73462306a36Sopenharmony_ci{
73562306a36Sopenharmony_ci	struct hsi_client *cl = to_hsi_client(dev);
73662306a36Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(cl);
73762306a36Sopenharmony_ci	dev_t hsc_dev = cl_data->cdev.dev;
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	cdev_del(&cl_data->cdev);
74062306a36Sopenharmony_ci	unregister_chrdev_region(hsc_dev, HSC_DEVS);
74162306a36Sopenharmony_ci	hsi_client_set_drvdata(cl, NULL);
74262306a36Sopenharmony_ci	kfree(cl_data);
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	return 0;
74562306a36Sopenharmony_ci}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_cistatic struct hsi_client_driver hsc_driver = {
74862306a36Sopenharmony_ci	.driver = {
74962306a36Sopenharmony_ci		.name	= "hsi_char",
75062306a36Sopenharmony_ci		.owner	= THIS_MODULE,
75162306a36Sopenharmony_ci		.probe	= hsc_probe,
75262306a36Sopenharmony_ci		.remove	= hsc_remove,
75362306a36Sopenharmony_ci	},
75462306a36Sopenharmony_ci};
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_cistatic int __init hsc_init(void)
75762306a36Sopenharmony_ci{
75862306a36Sopenharmony_ci	int ret;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	if ((max_data_size < 4) || (max_data_size > 0x10000) ||
76162306a36Sopenharmony_ci		(max_data_size & (max_data_size - 1))) {
76262306a36Sopenharmony_ci		pr_err("Invalid max read/write data size\n");
76362306a36Sopenharmony_ci		return -EINVAL;
76462306a36Sopenharmony_ci	}
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci	ret = hsi_register_client_driver(&hsc_driver);
76762306a36Sopenharmony_ci	if (ret) {
76862306a36Sopenharmony_ci		pr_err("Error while registering HSI/SSI driver %d\n", ret);
76962306a36Sopenharmony_ci		return ret;
77062306a36Sopenharmony_ci	}
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	pr_info("HSI/SSI char device loaded\n");
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	return 0;
77562306a36Sopenharmony_ci}
77662306a36Sopenharmony_cimodule_init(hsc_init);
77762306a36Sopenharmony_ci
77862306a36Sopenharmony_cistatic void __exit hsc_exit(void)
77962306a36Sopenharmony_ci{
78062306a36Sopenharmony_ci	hsi_unregister_client_driver(&hsc_driver);
78162306a36Sopenharmony_ci	pr_info("HSI char device removed\n");
78262306a36Sopenharmony_ci}
78362306a36Sopenharmony_cimodule_exit(hsc_exit);
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ciMODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>");
78662306a36Sopenharmony_ciMODULE_ALIAS("hsi:hsi_char");
78762306a36Sopenharmony_ciMODULE_DESCRIPTION("HSI character device");
78862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
789