18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * HSI character device driver, implements the character device
48c2ecf20Sopenharmony_ci * interface.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2010 Nokia Corporation. All rights reserved.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Contact: Andras Domokos <andras.domokos@nokia.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/errno.h>
128c2ecf20Sopenharmony_ci#include <linux/types.h>
138c2ecf20Sopenharmony_ci#include <linux/atomic.h>
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/init.h>
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/mutex.h>
188c2ecf20Sopenharmony_ci#include <linux/list.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include <linux/kmemleak.h>
218c2ecf20Sopenharmony_ci#include <linux/ioctl.h>
228c2ecf20Sopenharmony_ci#include <linux/wait.h>
238c2ecf20Sopenharmony_ci#include <linux/fs.h>
248c2ecf20Sopenharmony_ci#include <linux/sched.h>
258c2ecf20Sopenharmony_ci#include <linux/device.h>
268c2ecf20Sopenharmony_ci#include <linux/cdev.h>
278c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
288c2ecf20Sopenharmony_ci#include <linux/scatterlist.h>
298c2ecf20Sopenharmony_ci#include <linux/stat.h>
308c2ecf20Sopenharmony_ci#include <linux/hsi/hsi.h>
318c2ecf20Sopenharmony_ci#include <linux/hsi/hsi_char.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define HSC_DEVS		16 /* Num of channels */
348c2ecf20Sopenharmony_ci#define HSC_MSGS		4
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define HSC_RXBREAK		0
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define HSC_ID_BITS		6
398c2ecf20Sopenharmony_ci#define HSC_PORT_ID_BITS	4
408c2ecf20Sopenharmony_ci#define HSC_ID_MASK		3
418c2ecf20Sopenharmony_ci#define HSC_PORT_ID_MASK	3
428c2ecf20Sopenharmony_ci#define HSC_CH_MASK		0xf
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/*
458c2ecf20Sopenharmony_ci * We support up to 4 controllers that can have up to 4
468c2ecf20Sopenharmony_ci * ports, which should currently be more than enough.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_ci#define HSC_BASEMINOR(id, port_id) \
498c2ecf20Sopenharmony_ci		((((id) & HSC_ID_MASK) << HSC_ID_BITS) | \
508c2ecf20Sopenharmony_ci		(((port_id) & HSC_PORT_ID_MASK) << HSC_PORT_ID_BITS))
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cienum {
538c2ecf20Sopenharmony_ci	HSC_CH_OPEN,
548c2ecf20Sopenharmony_ci	HSC_CH_READ,
558c2ecf20Sopenharmony_ci	HSC_CH_WRITE,
568c2ecf20Sopenharmony_ci	HSC_CH_WLINE,
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cienum {
608c2ecf20Sopenharmony_ci	HSC_RX,
618c2ecf20Sopenharmony_ci	HSC_TX,
628c2ecf20Sopenharmony_ci};
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistruct hsc_client_data;
658c2ecf20Sopenharmony_ci/**
668c2ecf20Sopenharmony_ci * struct hsc_channel - hsi_char internal channel data
678c2ecf20Sopenharmony_ci * @ch: channel number
688c2ecf20Sopenharmony_ci * @flags: Keeps state of the channel (open/close, reading, writing)
698c2ecf20Sopenharmony_ci * @free_msgs_list: List of free HSI messages/requests
708c2ecf20Sopenharmony_ci * @rx_msgs_queue: List of pending RX requests
718c2ecf20Sopenharmony_ci * @tx_msgs_queue: List of pending TX requests
728c2ecf20Sopenharmony_ci * @lock: Serialize access to the lists
738c2ecf20Sopenharmony_ci * @cl: reference to the associated hsi_client
748c2ecf20Sopenharmony_ci * @cl_data: reference to the client data that this channels belongs to
758c2ecf20Sopenharmony_ci * @rx_wait: RX requests wait queue
768c2ecf20Sopenharmony_ci * @tx_wait: TX requests wait queue
778c2ecf20Sopenharmony_ci */
788c2ecf20Sopenharmony_cistruct hsc_channel {
798c2ecf20Sopenharmony_ci	unsigned int		ch;
808c2ecf20Sopenharmony_ci	unsigned long		flags;
818c2ecf20Sopenharmony_ci	struct list_head	free_msgs_list;
828c2ecf20Sopenharmony_ci	struct list_head	rx_msgs_queue;
838c2ecf20Sopenharmony_ci	struct list_head	tx_msgs_queue;
848c2ecf20Sopenharmony_ci	spinlock_t		lock;
858c2ecf20Sopenharmony_ci	struct hsi_client	*cl;
868c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data;
878c2ecf20Sopenharmony_ci	wait_queue_head_t	rx_wait;
888c2ecf20Sopenharmony_ci	wait_queue_head_t	tx_wait;
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci/**
928c2ecf20Sopenharmony_ci * struct hsc_client_data - hsi_char internal client data
938c2ecf20Sopenharmony_ci * @cdev: Characther device associated to the hsi_client
948c2ecf20Sopenharmony_ci * @lock: Lock to serialize open/close access
958c2ecf20Sopenharmony_ci * @flags: Keeps track of port state (rx hwbreak armed)
968c2ecf20Sopenharmony_ci * @usecnt: Use count for claiming the HSI port (mutex protected)
978c2ecf20Sopenharmony_ci * @cl: Referece to the HSI client
988c2ecf20Sopenharmony_ci * @channels: Array of channels accessible by the client
998c2ecf20Sopenharmony_ci */
1008c2ecf20Sopenharmony_cistruct hsc_client_data {
1018c2ecf20Sopenharmony_ci	struct cdev		cdev;
1028c2ecf20Sopenharmony_ci	struct mutex		lock;
1038c2ecf20Sopenharmony_ci	unsigned long		flags;
1048c2ecf20Sopenharmony_ci	unsigned int		usecnt;
1058c2ecf20Sopenharmony_ci	struct hsi_client	*cl;
1068c2ecf20Sopenharmony_ci	struct hsc_channel	channels[HSC_DEVS];
1078c2ecf20Sopenharmony_ci};
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci/* Stores the major number dynamically allocated for hsi_char */
1108c2ecf20Sopenharmony_cistatic unsigned int hsc_major;
1118c2ecf20Sopenharmony_ci/* Maximum buffer size that hsi_char will accept from userspace */
1128c2ecf20Sopenharmony_cistatic unsigned int max_data_size = 0x1000;
1138c2ecf20Sopenharmony_cimodule_param(max_data_size, uint, 0);
1148c2ecf20Sopenharmony_ciMODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)");
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg,
1178c2ecf20Sopenharmony_ci							struct list_head *queue)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	unsigned long flags;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
1228c2ecf20Sopenharmony_ci	list_add_tail(&msg->link, queue);
1238c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic struct hsi_msg *hsc_get_first_msg(struct hsc_channel *channel,
1278c2ecf20Sopenharmony_ci							struct list_head *queue)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct hsi_msg *msg = NULL;
1308c2ecf20Sopenharmony_ci	unsigned long flags;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (list_empty(queue))
1358c2ecf20Sopenharmony_ci		goto out;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	msg = list_first_entry(queue, struct hsi_msg, link);
1388c2ecf20Sopenharmony_ci	list_del(&msg->link);
1398c2ecf20Sopenharmony_ciout:
1408c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return msg;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic inline void hsc_msg_free(struct hsi_msg *msg)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	kfree(sg_virt(msg->sgt.sgl));
1488c2ecf20Sopenharmony_ci	hsi_free_msg(msg);
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic void hsc_free_list(struct list_head *list)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct hsi_msg *msg, *tmp;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	list_for_each_entry_safe(msg, tmp, list, link) {
1568c2ecf20Sopenharmony_ci		list_del(&msg->link);
1578c2ecf20Sopenharmony_ci		hsc_msg_free(msg);
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic void hsc_reset_list(struct hsc_channel *channel, struct list_head *l)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	unsigned long flags;
1648c2ecf20Sopenharmony_ci	LIST_HEAD(list);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	spin_lock_irqsave(&channel->lock, flags);
1678c2ecf20Sopenharmony_ci	list_splice_init(l, &list);
1688c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&channel->lock, flags);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	hsc_free_list(&list);
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic inline struct hsi_msg *hsc_msg_alloc(unsigned int alloc_size)
1748c2ecf20Sopenharmony_ci{
1758c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
1768c2ecf20Sopenharmony_ci	void *buf;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	msg = hsi_alloc_msg(1, GFP_KERNEL);
1798c2ecf20Sopenharmony_ci	if (!msg)
1808c2ecf20Sopenharmony_ci		goto out;
1818c2ecf20Sopenharmony_ci	buf = kmalloc(alloc_size, GFP_KERNEL);
1828c2ecf20Sopenharmony_ci	if (!buf) {
1838c2ecf20Sopenharmony_ci		hsi_free_msg(msg);
1848c2ecf20Sopenharmony_ci		goto out;
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci	sg_init_one(msg->sgt.sgl, buf, alloc_size);
1878c2ecf20Sopenharmony_ci	/* Ignore false positive, due to sg pointer handling */
1888c2ecf20Sopenharmony_ci	kmemleak_ignore(buf);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	return msg;
1918c2ecf20Sopenharmony_ciout:
1928c2ecf20Sopenharmony_ci	return NULL;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic inline int hsc_msgs_alloc(struct hsc_channel *channel)
1968c2ecf20Sopenharmony_ci{
1978c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
1988c2ecf20Sopenharmony_ci	int i;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	for (i = 0; i < HSC_MSGS; i++) {
2018c2ecf20Sopenharmony_ci		msg = hsc_msg_alloc(max_data_size);
2028c2ecf20Sopenharmony_ci		if (!msg)
2038c2ecf20Sopenharmony_ci			goto out;
2048c2ecf20Sopenharmony_ci		msg->channel = channel->ch;
2058c2ecf20Sopenharmony_ci		list_add_tail(&msg->link, &channel->free_msgs_list);
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	return 0;
2098c2ecf20Sopenharmony_ciout:
2108c2ecf20Sopenharmony_ci	hsc_free_list(&channel->free_msgs_list);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	return -ENOMEM;
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic inline unsigned int hsc_msg_len_get(struct hsi_msg *msg)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	return msg->sgt.sgl->length;
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic inline void hsc_msg_len_set(struct hsi_msg *msg, unsigned int len)
2218c2ecf20Sopenharmony_ci{
2228c2ecf20Sopenharmony_ci	msg->sgt.sgl->length = len;
2238c2ecf20Sopenharmony_ci}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic void hsc_rx_completed(struct hsi_msg *msg)
2268c2ecf20Sopenharmony_ci{
2278c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
2288c2ecf20Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels + msg->channel;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	if (test_bit(HSC_CH_READ, &channel->flags)) {
2318c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->rx_msgs_queue);
2328c2ecf20Sopenharmony_ci		wake_up(&channel->rx_wait);
2338c2ecf20Sopenharmony_ci	} else {
2348c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
2358c2ecf20Sopenharmony_ci	}
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic void hsc_rx_msg_destructor(struct hsi_msg *msg)
2398c2ecf20Sopenharmony_ci{
2408c2ecf20Sopenharmony_ci	msg->status = HSI_STATUS_ERROR;
2418c2ecf20Sopenharmony_ci	hsc_msg_len_set(msg, 0);
2428c2ecf20Sopenharmony_ci	hsc_rx_completed(msg);
2438c2ecf20Sopenharmony_ci}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cistatic void hsc_tx_completed(struct hsi_msg *msg)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
2488c2ecf20Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels + msg->channel;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	if (test_bit(HSC_CH_WRITE, &channel->flags)) {
2518c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->tx_msgs_queue);
2528c2ecf20Sopenharmony_ci		wake_up(&channel->tx_wait);
2538c2ecf20Sopenharmony_ci	} else {
2548c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic void hsc_tx_msg_destructor(struct hsi_msg *msg)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	msg->status = HSI_STATUS_ERROR;
2618c2ecf20Sopenharmony_ci	hsc_msg_len_set(msg, 0);
2628c2ecf20Sopenharmony_ci	hsc_tx_completed(msg);
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic void hsc_break_req_destructor(struct hsi_msg *msg)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	hsi_free_msg(msg);
2708c2ecf20Sopenharmony_ci	clear_bit(HSC_RXBREAK, &cl_data->flags);
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic void hsc_break_received(struct hsi_msg *msg)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(msg->cl);
2768c2ecf20Sopenharmony_ci	struct hsc_channel *channel = cl_data->channels;
2778c2ecf20Sopenharmony_ci	int i, ret;
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	/* Broadcast HWBREAK on all channels */
2808c2ecf20Sopenharmony_ci	for (i = 0; i < HSC_DEVS; i++, channel++) {
2818c2ecf20Sopenharmony_ci		struct hsi_msg *msg2;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci		if (!test_bit(HSC_CH_READ, &channel->flags))
2848c2ecf20Sopenharmony_ci			continue;
2858c2ecf20Sopenharmony_ci		msg2 = hsc_get_first_msg(channel, &channel->free_msgs_list);
2868c2ecf20Sopenharmony_ci		if (!msg2)
2878c2ecf20Sopenharmony_ci			continue;
2888c2ecf20Sopenharmony_ci		clear_bit(HSC_CH_READ, &channel->flags);
2898c2ecf20Sopenharmony_ci		hsc_msg_len_set(msg2, 0);
2908c2ecf20Sopenharmony_ci		msg2->status = HSI_STATUS_COMPLETED;
2918c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg2, &channel->rx_msgs_queue);
2928c2ecf20Sopenharmony_ci		wake_up(&channel->rx_wait);
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci	hsi_flush(msg->cl);
2958c2ecf20Sopenharmony_ci	ret = hsi_async_read(msg->cl, msg);
2968c2ecf20Sopenharmony_ci	if (ret < 0)
2978c2ecf20Sopenharmony_ci		hsc_break_req_destructor(msg);
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistatic int hsc_break_request(struct hsi_client *cl)
3018c2ecf20Sopenharmony_ci{
3028c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(cl);
3038c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
3048c2ecf20Sopenharmony_ci	int ret;
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	if (test_and_set_bit(HSC_RXBREAK, &cl_data->flags))
3078c2ecf20Sopenharmony_ci		return -EBUSY;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	msg = hsi_alloc_msg(0, GFP_KERNEL);
3108c2ecf20Sopenharmony_ci	if (!msg) {
3118c2ecf20Sopenharmony_ci		clear_bit(HSC_RXBREAK, &cl_data->flags);
3128c2ecf20Sopenharmony_ci		return -ENOMEM;
3138c2ecf20Sopenharmony_ci	}
3148c2ecf20Sopenharmony_ci	msg->break_frame = 1;
3158c2ecf20Sopenharmony_ci	msg->complete = hsc_break_received;
3168c2ecf20Sopenharmony_ci	msg->destructor = hsc_break_req_destructor;
3178c2ecf20Sopenharmony_ci	ret = hsi_async_read(cl, msg);
3188c2ecf20Sopenharmony_ci	if (ret < 0)
3198c2ecf20Sopenharmony_ci		hsc_break_req_destructor(msg);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	return ret;
3228c2ecf20Sopenharmony_ci}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_cistatic int hsc_break_send(struct hsi_client *cl)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
3278c2ecf20Sopenharmony_ci	int ret;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	msg = hsi_alloc_msg(0, GFP_ATOMIC);
3308c2ecf20Sopenharmony_ci	if (!msg)
3318c2ecf20Sopenharmony_ci		return -ENOMEM;
3328c2ecf20Sopenharmony_ci	msg->break_frame = 1;
3338c2ecf20Sopenharmony_ci	msg->complete = hsi_free_msg;
3348c2ecf20Sopenharmony_ci	msg->destructor = hsi_free_msg;
3358c2ecf20Sopenharmony_ci	ret = hsi_async_write(cl, msg);
3368c2ecf20Sopenharmony_ci	if (ret < 0)
3378c2ecf20Sopenharmony_ci		hsi_free_msg(msg);
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	return ret;
3408c2ecf20Sopenharmony_ci}
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_cistatic int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc)
3438c2ecf20Sopenharmony_ci{
3448c2ecf20Sopenharmony_ci	struct hsi_config tmp;
3458c2ecf20Sopenharmony_ci	int ret;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	if ((rxc->mode != HSI_MODE_STREAM) && (rxc->mode != HSI_MODE_FRAME))
3488c2ecf20Sopenharmony_ci		return -EINVAL;
3498c2ecf20Sopenharmony_ci	if ((rxc->channels == 0) || (rxc->channels > HSC_DEVS))
3508c2ecf20Sopenharmony_ci		return -EINVAL;
3518c2ecf20Sopenharmony_ci	if (rxc->channels & (rxc->channels - 1))
3528c2ecf20Sopenharmony_ci		return -EINVAL;
3538c2ecf20Sopenharmony_ci	if ((rxc->flow != HSI_FLOW_SYNC) && (rxc->flow != HSI_FLOW_PIPE))
3548c2ecf20Sopenharmony_ci		return -EINVAL;
3558c2ecf20Sopenharmony_ci	tmp = cl->rx_cfg;
3568c2ecf20Sopenharmony_ci	cl->rx_cfg.mode = rxc->mode;
3578c2ecf20Sopenharmony_ci	cl->rx_cfg.num_hw_channels = rxc->channels;
3588c2ecf20Sopenharmony_ci	cl->rx_cfg.flow = rxc->flow;
3598c2ecf20Sopenharmony_ci	ret = hsi_setup(cl);
3608c2ecf20Sopenharmony_ci	if (ret < 0) {
3618c2ecf20Sopenharmony_ci		cl->rx_cfg = tmp;
3628c2ecf20Sopenharmony_ci		return ret;
3638c2ecf20Sopenharmony_ci	}
3648c2ecf20Sopenharmony_ci	if (rxc->mode == HSI_MODE_FRAME)
3658c2ecf20Sopenharmony_ci		hsc_break_request(cl);
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	return ret;
3688c2ecf20Sopenharmony_ci}
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_cistatic inline void hsc_rx_get(struct hsi_client *cl, struct hsc_rx_config *rxc)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	rxc->mode = cl->rx_cfg.mode;
3738c2ecf20Sopenharmony_ci	rxc->channels = cl->rx_cfg.num_hw_channels;
3748c2ecf20Sopenharmony_ci	rxc->flow = cl->rx_cfg.flow;
3758c2ecf20Sopenharmony_ci}
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_cistatic int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc)
3788c2ecf20Sopenharmony_ci{
3798c2ecf20Sopenharmony_ci	struct hsi_config tmp;
3808c2ecf20Sopenharmony_ci	int ret;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	if ((txc->mode != HSI_MODE_STREAM) && (txc->mode != HSI_MODE_FRAME))
3838c2ecf20Sopenharmony_ci		return -EINVAL;
3848c2ecf20Sopenharmony_ci	if ((txc->channels == 0) || (txc->channels > HSC_DEVS))
3858c2ecf20Sopenharmony_ci		return -EINVAL;
3868c2ecf20Sopenharmony_ci	if (txc->channels & (txc->channels - 1))
3878c2ecf20Sopenharmony_ci		return -EINVAL;
3888c2ecf20Sopenharmony_ci	if ((txc->arb_mode != HSI_ARB_RR) && (txc->arb_mode != HSI_ARB_PRIO))
3898c2ecf20Sopenharmony_ci		return -EINVAL;
3908c2ecf20Sopenharmony_ci	tmp = cl->tx_cfg;
3918c2ecf20Sopenharmony_ci	cl->tx_cfg.mode = txc->mode;
3928c2ecf20Sopenharmony_ci	cl->tx_cfg.num_hw_channels = txc->channels;
3938c2ecf20Sopenharmony_ci	cl->tx_cfg.speed = txc->speed;
3948c2ecf20Sopenharmony_ci	cl->tx_cfg.arb_mode = txc->arb_mode;
3958c2ecf20Sopenharmony_ci	ret = hsi_setup(cl);
3968c2ecf20Sopenharmony_ci	if (ret < 0) {
3978c2ecf20Sopenharmony_ci		cl->tx_cfg = tmp;
3988c2ecf20Sopenharmony_ci		return ret;
3998c2ecf20Sopenharmony_ci	}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	return ret;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic inline void hsc_tx_get(struct hsi_client *cl, struct hsc_tx_config *txc)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	txc->mode = cl->tx_cfg.mode;
4078c2ecf20Sopenharmony_ci	txc->channels = cl->tx_cfg.num_hw_channels;
4088c2ecf20Sopenharmony_ci	txc->speed = cl->tx_cfg.speed;
4098c2ecf20Sopenharmony_ci	txc->arb_mode = cl->tx_cfg.arb_mode;
4108c2ecf20Sopenharmony_ci}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic ssize_t hsc_read(struct file *file, char __user *buf, size_t len,
4138c2ecf20Sopenharmony_ci						loff_t *ppos __maybe_unused)
4148c2ecf20Sopenharmony_ci{
4158c2ecf20Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
4168c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
4178c2ecf20Sopenharmony_ci	ssize_t ret;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	if (len == 0)
4208c2ecf20Sopenharmony_ci		return 0;
4218c2ecf20Sopenharmony_ci	if (!IS_ALIGNED(len, sizeof(u32)))
4228c2ecf20Sopenharmony_ci		return -EINVAL;
4238c2ecf20Sopenharmony_ci	if (len > max_data_size)
4248c2ecf20Sopenharmony_ci		len = max_data_size;
4258c2ecf20Sopenharmony_ci	if (channel->ch >= channel->cl->rx_cfg.num_hw_channels)
4268c2ecf20Sopenharmony_ci		return -ECHRNG;
4278c2ecf20Sopenharmony_ci	if (test_and_set_bit(HSC_CH_READ, &channel->flags))
4288c2ecf20Sopenharmony_ci		return -EBUSY;
4298c2ecf20Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->free_msgs_list);
4308c2ecf20Sopenharmony_ci	if (!msg) {
4318c2ecf20Sopenharmony_ci		ret = -ENOSPC;
4328c2ecf20Sopenharmony_ci		goto out;
4338c2ecf20Sopenharmony_ci	}
4348c2ecf20Sopenharmony_ci	hsc_msg_len_set(msg, len);
4358c2ecf20Sopenharmony_ci	msg->complete = hsc_rx_completed;
4368c2ecf20Sopenharmony_ci	msg->destructor = hsc_rx_msg_destructor;
4378c2ecf20Sopenharmony_ci	ret = hsi_async_read(channel->cl, msg);
4388c2ecf20Sopenharmony_ci	if (ret < 0) {
4398c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
4408c2ecf20Sopenharmony_ci		goto out;
4418c2ecf20Sopenharmony_ci	}
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_ci	ret = wait_event_interruptible(channel->rx_wait,
4448c2ecf20Sopenharmony_ci					!list_empty(&channel->rx_msgs_queue));
4458c2ecf20Sopenharmony_ci	if (ret < 0) {
4468c2ecf20Sopenharmony_ci		clear_bit(HSC_CH_READ, &channel->flags);
4478c2ecf20Sopenharmony_ci		hsi_flush(channel->cl);
4488c2ecf20Sopenharmony_ci		return -EINTR;
4498c2ecf20Sopenharmony_ci	}
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->rx_msgs_queue);
4528c2ecf20Sopenharmony_ci	if (msg) {
4538c2ecf20Sopenharmony_ci		if (msg->status != HSI_STATUS_ERROR) {
4548c2ecf20Sopenharmony_ci			ret = copy_to_user((void __user *)buf,
4558c2ecf20Sopenharmony_ci			sg_virt(msg->sgt.sgl), hsc_msg_len_get(msg));
4568c2ecf20Sopenharmony_ci			if (ret)
4578c2ecf20Sopenharmony_ci				ret = -EFAULT;
4588c2ecf20Sopenharmony_ci			else
4598c2ecf20Sopenharmony_ci				ret = hsc_msg_len_get(msg);
4608c2ecf20Sopenharmony_ci		} else {
4618c2ecf20Sopenharmony_ci			ret = -EIO;
4628c2ecf20Sopenharmony_ci		}
4638c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
4648c2ecf20Sopenharmony_ci	}
4658c2ecf20Sopenharmony_ciout:
4668c2ecf20Sopenharmony_ci	clear_bit(HSC_CH_READ, &channel->flags);
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	return ret;
4698c2ecf20Sopenharmony_ci}
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_cistatic ssize_t hsc_write(struct file *file, const char __user *buf, size_t len,
4728c2ecf20Sopenharmony_ci						loff_t *ppos __maybe_unused)
4738c2ecf20Sopenharmony_ci{
4748c2ecf20Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
4758c2ecf20Sopenharmony_ci	struct hsi_msg *msg;
4768c2ecf20Sopenharmony_ci	ssize_t ret;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	if ((len == 0) || !IS_ALIGNED(len, sizeof(u32)))
4798c2ecf20Sopenharmony_ci		return -EINVAL;
4808c2ecf20Sopenharmony_ci	if (len > max_data_size)
4818c2ecf20Sopenharmony_ci		len = max_data_size;
4828c2ecf20Sopenharmony_ci	if (channel->ch >= channel->cl->tx_cfg.num_hw_channels)
4838c2ecf20Sopenharmony_ci		return -ECHRNG;
4848c2ecf20Sopenharmony_ci	if (test_and_set_bit(HSC_CH_WRITE, &channel->flags))
4858c2ecf20Sopenharmony_ci		return -EBUSY;
4868c2ecf20Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->free_msgs_list);
4878c2ecf20Sopenharmony_ci	if (!msg) {
4888c2ecf20Sopenharmony_ci		clear_bit(HSC_CH_WRITE, &channel->flags);
4898c2ecf20Sopenharmony_ci		return -ENOSPC;
4908c2ecf20Sopenharmony_ci	}
4918c2ecf20Sopenharmony_ci	if (copy_from_user(sg_virt(msg->sgt.sgl), (void __user *)buf, len)) {
4928c2ecf20Sopenharmony_ci		ret = -EFAULT;
4938c2ecf20Sopenharmony_ci		goto out;
4948c2ecf20Sopenharmony_ci	}
4958c2ecf20Sopenharmony_ci	hsc_msg_len_set(msg, len);
4968c2ecf20Sopenharmony_ci	msg->complete = hsc_tx_completed;
4978c2ecf20Sopenharmony_ci	msg->destructor = hsc_tx_msg_destructor;
4988c2ecf20Sopenharmony_ci	ret = hsi_async_write(channel->cl, msg);
4998c2ecf20Sopenharmony_ci	if (ret < 0)
5008c2ecf20Sopenharmony_ci		goto out;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	ret = wait_event_interruptible(channel->tx_wait,
5038c2ecf20Sopenharmony_ci					!list_empty(&channel->tx_msgs_queue));
5048c2ecf20Sopenharmony_ci	if (ret < 0) {
5058c2ecf20Sopenharmony_ci		clear_bit(HSC_CH_WRITE, &channel->flags);
5068c2ecf20Sopenharmony_ci		hsi_flush(channel->cl);
5078c2ecf20Sopenharmony_ci		return -EINTR;
5088c2ecf20Sopenharmony_ci	}
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	msg = hsc_get_first_msg(channel, &channel->tx_msgs_queue);
5118c2ecf20Sopenharmony_ci	if (msg) {
5128c2ecf20Sopenharmony_ci		if (msg->status == HSI_STATUS_ERROR)
5138c2ecf20Sopenharmony_ci			ret = -EIO;
5148c2ecf20Sopenharmony_ci		else
5158c2ecf20Sopenharmony_ci			ret = hsc_msg_len_get(msg);
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci		hsc_add_tail(channel, msg, &channel->free_msgs_list);
5188c2ecf20Sopenharmony_ci	}
5198c2ecf20Sopenharmony_ciout:
5208c2ecf20Sopenharmony_ci	clear_bit(HSC_CH_WRITE, &channel->flags);
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	return ret;
5238c2ecf20Sopenharmony_ci}
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_cistatic long hsc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
5268c2ecf20Sopenharmony_ci{
5278c2ecf20Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
5288c2ecf20Sopenharmony_ci	unsigned int state;
5298c2ecf20Sopenharmony_ci	struct hsc_rx_config rxc;
5308c2ecf20Sopenharmony_ci	struct hsc_tx_config txc;
5318c2ecf20Sopenharmony_ci	long ret = 0;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	switch (cmd) {
5348c2ecf20Sopenharmony_ci	case HSC_RESET:
5358c2ecf20Sopenharmony_ci		hsi_flush(channel->cl);
5368c2ecf20Sopenharmony_ci		break;
5378c2ecf20Sopenharmony_ci	case HSC_SET_PM:
5388c2ecf20Sopenharmony_ci		if (copy_from_user(&state, (void __user *)arg, sizeof(state)))
5398c2ecf20Sopenharmony_ci			return -EFAULT;
5408c2ecf20Sopenharmony_ci		if (state == HSC_PM_DISABLE) {
5418c2ecf20Sopenharmony_ci			if (test_and_set_bit(HSC_CH_WLINE, &channel->flags))
5428c2ecf20Sopenharmony_ci				return -EINVAL;
5438c2ecf20Sopenharmony_ci			ret = hsi_start_tx(channel->cl);
5448c2ecf20Sopenharmony_ci		} else if (state == HSC_PM_ENABLE) {
5458c2ecf20Sopenharmony_ci			if (!test_and_clear_bit(HSC_CH_WLINE, &channel->flags))
5468c2ecf20Sopenharmony_ci				return -EINVAL;
5478c2ecf20Sopenharmony_ci			ret = hsi_stop_tx(channel->cl);
5488c2ecf20Sopenharmony_ci		} else {
5498c2ecf20Sopenharmony_ci			ret = -EINVAL;
5508c2ecf20Sopenharmony_ci		}
5518c2ecf20Sopenharmony_ci		break;
5528c2ecf20Sopenharmony_ci	case HSC_SEND_BREAK:
5538c2ecf20Sopenharmony_ci		return hsc_break_send(channel->cl);
5548c2ecf20Sopenharmony_ci	case HSC_SET_RX:
5558c2ecf20Sopenharmony_ci		if (copy_from_user(&rxc, (void __user *)arg, sizeof(rxc)))
5568c2ecf20Sopenharmony_ci			return -EFAULT;
5578c2ecf20Sopenharmony_ci		return hsc_rx_set(channel->cl, &rxc);
5588c2ecf20Sopenharmony_ci	case HSC_GET_RX:
5598c2ecf20Sopenharmony_ci		hsc_rx_get(channel->cl, &rxc);
5608c2ecf20Sopenharmony_ci		if (copy_to_user((void __user *)arg, &rxc, sizeof(rxc)))
5618c2ecf20Sopenharmony_ci			return -EFAULT;
5628c2ecf20Sopenharmony_ci		break;
5638c2ecf20Sopenharmony_ci	case HSC_SET_TX:
5648c2ecf20Sopenharmony_ci		if (copy_from_user(&txc, (void __user *)arg, sizeof(txc)))
5658c2ecf20Sopenharmony_ci			return -EFAULT;
5668c2ecf20Sopenharmony_ci		return hsc_tx_set(channel->cl, &txc);
5678c2ecf20Sopenharmony_ci	case HSC_GET_TX:
5688c2ecf20Sopenharmony_ci		hsc_tx_get(channel->cl, &txc);
5698c2ecf20Sopenharmony_ci		if (copy_to_user((void __user *)arg, &txc, sizeof(txc)))
5708c2ecf20Sopenharmony_ci			return -EFAULT;
5718c2ecf20Sopenharmony_ci		break;
5728c2ecf20Sopenharmony_ci	default:
5738c2ecf20Sopenharmony_ci		return -ENOIOCTLCMD;
5748c2ecf20Sopenharmony_ci	}
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci	return ret;
5778c2ecf20Sopenharmony_ci}
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_cistatic inline void __hsc_port_release(struct hsc_client_data *cl_data)
5808c2ecf20Sopenharmony_ci{
5818c2ecf20Sopenharmony_ci	BUG_ON(cl_data->usecnt == 0);
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci	if (--cl_data->usecnt == 0) {
5848c2ecf20Sopenharmony_ci		hsi_flush(cl_data->cl);
5858c2ecf20Sopenharmony_ci		hsi_release_port(cl_data->cl);
5868c2ecf20Sopenharmony_ci	}
5878c2ecf20Sopenharmony_ci}
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_cistatic int hsc_open(struct inode *inode, struct file *file)
5908c2ecf20Sopenharmony_ci{
5918c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data;
5928c2ecf20Sopenharmony_ci	struct hsc_channel *channel;
5938c2ecf20Sopenharmony_ci	int ret = 0;
5948c2ecf20Sopenharmony_ci
5958c2ecf20Sopenharmony_ci	pr_debug("open, minor = %d\n", iminor(inode));
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci	cl_data = container_of(inode->i_cdev, struct hsc_client_data, cdev);
5988c2ecf20Sopenharmony_ci	mutex_lock(&cl_data->lock);
5998c2ecf20Sopenharmony_ci	channel = cl_data->channels + (iminor(inode) & HSC_CH_MASK);
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	if (test_and_set_bit(HSC_CH_OPEN, &channel->flags)) {
6028c2ecf20Sopenharmony_ci		ret = -EBUSY;
6038c2ecf20Sopenharmony_ci		goto out;
6048c2ecf20Sopenharmony_ci	}
6058c2ecf20Sopenharmony_ci	/*
6068c2ecf20Sopenharmony_ci	 * Check if we have already claimed the port associated to the HSI
6078c2ecf20Sopenharmony_ci	 * client. If not then try to claim it, else increase its refcount
6088c2ecf20Sopenharmony_ci	 */
6098c2ecf20Sopenharmony_ci	if (cl_data->usecnt == 0) {
6108c2ecf20Sopenharmony_ci		ret = hsi_claim_port(cl_data->cl, 0);
6118c2ecf20Sopenharmony_ci		if (ret < 0)
6128c2ecf20Sopenharmony_ci			goto out;
6138c2ecf20Sopenharmony_ci		hsi_setup(cl_data->cl);
6148c2ecf20Sopenharmony_ci	}
6158c2ecf20Sopenharmony_ci	cl_data->usecnt++;
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_ci	ret = hsc_msgs_alloc(channel);
6188c2ecf20Sopenharmony_ci	if (ret < 0) {
6198c2ecf20Sopenharmony_ci		__hsc_port_release(cl_data);
6208c2ecf20Sopenharmony_ci		goto out;
6218c2ecf20Sopenharmony_ci	}
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci	file->private_data = channel;
6248c2ecf20Sopenharmony_ci	mutex_unlock(&cl_data->lock);
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci	return ret;
6278c2ecf20Sopenharmony_ciout:
6288c2ecf20Sopenharmony_ci	mutex_unlock(&cl_data->lock);
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	return ret;
6318c2ecf20Sopenharmony_ci}
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_cistatic int hsc_release(struct inode *inode __maybe_unused, struct file *file)
6348c2ecf20Sopenharmony_ci{
6358c2ecf20Sopenharmony_ci	struct hsc_channel *channel = file->private_data;
6368c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = channel->cl_data;
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci	mutex_lock(&cl_data->lock);
6398c2ecf20Sopenharmony_ci	file->private_data = NULL;
6408c2ecf20Sopenharmony_ci	if (test_and_clear_bit(HSC_CH_WLINE, &channel->flags))
6418c2ecf20Sopenharmony_ci		hsi_stop_tx(channel->cl);
6428c2ecf20Sopenharmony_ci	__hsc_port_release(cl_data);
6438c2ecf20Sopenharmony_ci	hsc_reset_list(channel, &channel->rx_msgs_queue);
6448c2ecf20Sopenharmony_ci	hsc_reset_list(channel, &channel->tx_msgs_queue);
6458c2ecf20Sopenharmony_ci	hsc_reset_list(channel, &channel->free_msgs_list);
6468c2ecf20Sopenharmony_ci	clear_bit(HSC_CH_READ, &channel->flags);
6478c2ecf20Sopenharmony_ci	clear_bit(HSC_CH_WRITE, &channel->flags);
6488c2ecf20Sopenharmony_ci	clear_bit(HSC_CH_OPEN, &channel->flags);
6498c2ecf20Sopenharmony_ci	wake_up(&channel->rx_wait);
6508c2ecf20Sopenharmony_ci	wake_up(&channel->tx_wait);
6518c2ecf20Sopenharmony_ci	mutex_unlock(&cl_data->lock);
6528c2ecf20Sopenharmony_ci
6538c2ecf20Sopenharmony_ci	return 0;
6548c2ecf20Sopenharmony_ci}
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_cistatic const struct file_operations hsc_fops = {
6578c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
6588c2ecf20Sopenharmony_ci	.read		= hsc_read,
6598c2ecf20Sopenharmony_ci	.write		= hsc_write,
6608c2ecf20Sopenharmony_ci	.unlocked_ioctl	= hsc_ioctl,
6618c2ecf20Sopenharmony_ci	.open		= hsc_open,
6628c2ecf20Sopenharmony_ci	.release	= hsc_release,
6638c2ecf20Sopenharmony_ci};
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_cistatic void hsc_channel_init(struct hsc_channel *channel)
6668c2ecf20Sopenharmony_ci{
6678c2ecf20Sopenharmony_ci	init_waitqueue_head(&channel->rx_wait);
6688c2ecf20Sopenharmony_ci	init_waitqueue_head(&channel->tx_wait);
6698c2ecf20Sopenharmony_ci	spin_lock_init(&channel->lock);
6708c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&channel->free_msgs_list);
6718c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&channel->rx_msgs_queue);
6728c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&channel->tx_msgs_queue);
6738c2ecf20Sopenharmony_ci}
6748c2ecf20Sopenharmony_ci
6758c2ecf20Sopenharmony_cistatic int hsc_probe(struct device *dev)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	const char devname[] = "hsi_char";
6788c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data;
6798c2ecf20Sopenharmony_ci	struct hsc_channel *channel;
6808c2ecf20Sopenharmony_ci	struct hsi_client *cl = to_hsi_client(dev);
6818c2ecf20Sopenharmony_ci	unsigned int hsc_baseminor;
6828c2ecf20Sopenharmony_ci	dev_t hsc_dev;
6838c2ecf20Sopenharmony_ci	int ret;
6848c2ecf20Sopenharmony_ci	int i;
6858c2ecf20Sopenharmony_ci
6868c2ecf20Sopenharmony_ci	cl_data = kzalloc(sizeof(*cl_data), GFP_KERNEL);
6878c2ecf20Sopenharmony_ci	if (!cl_data)
6888c2ecf20Sopenharmony_ci		return -ENOMEM;
6898c2ecf20Sopenharmony_ci
6908c2ecf20Sopenharmony_ci	hsc_baseminor = HSC_BASEMINOR(hsi_id(cl), hsi_port_id(cl));
6918c2ecf20Sopenharmony_ci	if (!hsc_major) {
6928c2ecf20Sopenharmony_ci		ret = alloc_chrdev_region(&hsc_dev, hsc_baseminor,
6938c2ecf20Sopenharmony_ci						HSC_DEVS, devname);
6948c2ecf20Sopenharmony_ci		if (ret == 0)
6958c2ecf20Sopenharmony_ci			hsc_major = MAJOR(hsc_dev);
6968c2ecf20Sopenharmony_ci	} else {
6978c2ecf20Sopenharmony_ci		hsc_dev = MKDEV(hsc_major, hsc_baseminor);
6988c2ecf20Sopenharmony_ci		ret = register_chrdev_region(hsc_dev, HSC_DEVS, devname);
6998c2ecf20Sopenharmony_ci	}
7008c2ecf20Sopenharmony_ci	if (ret < 0) {
7018c2ecf20Sopenharmony_ci		dev_err(dev, "Device %s allocation failed %d\n",
7028c2ecf20Sopenharmony_ci					hsc_major ? "minor" : "major", ret);
7038c2ecf20Sopenharmony_ci		goto out1;
7048c2ecf20Sopenharmony_ci	}
7058c2ecf20Sopenharmony_ci	mutex_init(&cl_data->lock);
7068c2ecf20Sopenharmony_ci	hsi_client_set_drvdata(cl, cl_data);
7078c2ecf20Sopenharmony_ci	cdev_init(&cl_data->cdev, &hsc_fops);
7088c2ecf20Sopenharmony_ci	cl_data->cdev.owner = THIS_MODULE;
7098c2ecf20Sopenharmony_ci	cl_data->cl = cl;
7108c2ecf20Sopenharmony_ci	for (i = 0, channel = cl_data->channels; i < HSC_DEVS; i++, channel++) {
7118c2ecf20Sopenharmony_ci		hsc_channel_init(channel);
7128c2ecf20Sopenharmony_ci		channel->ch = i;
7138c2ecf20Sopenharmony_ci		channel->cl = cl;
7148c2ecf20Sopenharmony_ci		channel->cl_data = cl_data;
7158c2ecf20Sopenharmony_ci	}
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci	/* 1 hsi client -> N char devices (one for each channel) */
7188c2ecf20Sopenharmony_ci	ret = cdev_add(&cl_data->cdev, hsc_dev, HSC_DEVS);
7198c2ecf20Sopenharmony_ci	if (ret) {
7208c2ecf20Sopenharmony_ci		dev_err(dev, "Could not add char device %d\n", ret);
7218c2ecf20Sopenharmony_ci		goto out2;
7228c2ecf20Sopenharmony_ci	}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	return 0;
7258c2ecf20Sopenharmony_ciout2:
7268c2ecf20Sopenharmony_ci	unregister_chrdev_region(hsc_dev, HSC_DEVS);
7278c2ecf20Sopenharmony_ciout1:
7288c2ecf20Sopenharmony_ci	kfree(cl_data);
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci	return ret;
7318c2ecf20Sopenharmony_ci}
7328c2ecf20Sopenharmony_ci
7338c2ecf20Sopenharmony_cistatic int hsc_remove(struct device *dev)
7348c2ecf20Sopenharmony_ci{
7358c2ecf20Sopenharmony_ci	struct hsi_client *cl = to_hsi_client(dev);
7368c2ecf20Sopenharmony_ci	struct hsc_client_data *cl_data = hsi_client_drvdata(cl);
7378c2ecf20Sopenharmony_ci	dev_t hsc_dev = cl_data->cdev.dev;
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_ci	cdev_del(&cl_data->cdev);
7408c2ecf20Sopenharmony_ci	unregister_chrdev_region(hsc_dev, HSC_DEVS);
7418c2ecf20Sopenharmony_ci	hsi_client_set_drvdata(cl, NULL);
7428c2ecf20Sopenharmony_ci	kfree(cl_data);
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_ci	return 0;
7458c2ecf20Sopenharmony_ci}
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_cistatic struct hsi_client_driver hsc_driver = {
7488c2ecf20Sopenharmony_ci	.driver = {
7498c2ecf20Sopenharmony_ci		.name	= "hsi_char",
7508c2ecf20Sopenharmony_ci		.owner	= THIS_MODULE,
7518c2ecf20Sopenharmony_ci		.probe	= hsc_probe,
7528c2ecf20Sopenharmony_ci		.remove	= hsc_remove,
7538c2ecf20Sopenharmony_ci	},
7548c2ecf20Sopenharmony_ci};
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_cistatic int __init hsc_init(void)
7578c2ecf20Sopenharmony_ci{
7588c2ecf20Sopenharmony_ci	int ret;
7598c2ecf20Sopenharmony_ci
7608c2ecf20Sopenharmony_ci	if ((max_data_size < 4) || (max_data_size > 0x10000) ||
7618c2ecf20Sopenharmony_ci		(max_data_size & (max_data_size - 1))) {
7628c2ecf20Sopenharmony_ci		pr_err("Invalid max read/write data size\n");
7638c2ecf20Sopenharmony_ci		return -EINVAL;
7648c2ecf20Sopenharmony_ci	}
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci	ret = hsi_register_client_driver(&hsc_driver);
7678c2ecf20Sopenharmony_ci	if (ret) {
7688c2ecf20Sopenharmony_ci		pr_err("Error while registering HSI/SSI driver %d\n", ret);
7698c2ecf20Sopenharmony_ci		return ret;
7708c2ecf20Sopenharmony_ci	}
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_ci	pr_info("HSI/SSI char device loaded\n");
7738c2ecf20Sopenharmony_ci
7748c2ecf20Sopenharmony_ci	return 0;
7758c2ecf20Sopenharmony_ci}
7768c2ecf20Sopenharmony_cimodule_init(hsc_init);
7778c2ecf20Sopenharmony_ci
7788c2ecf20Sopenharmony_cistatic void __exit hsc_exit(void)
7798c2ecf20Sopenharmony_ci{
7808c2ecf20Sopenharmony_ci	hsi_unregister_client_driver(&hsc_driver);
7818c2ecf20Sopenharmony_ci	pr_info("HSI char device removed\n");
7828c2ecf20Sopenharmony_ci}
7838c2ecf20Sopenharmony_cimodule_exit(hsc_exit);
7848c2ecf20Sopenharmony_ci
7858c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>");
7868c2ecf20Sopenharmony_ciMODULE_ALIAS("hsi:hsi_char");
7878c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HSI character device");
7888c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
789