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