summaryrefslogtreecommitdiffstats
path: root/recipes-kernel/hdm-i2c/files/hdm_i2c.c
blob: ba0263bb3d12685972669e7d886f4358cb2aac4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/*
 * hdm_i2c.c - Hardware Dependent Module for I2C Interface
 *
 * Copyright (C) 2013-2015, Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * This file is licensed under GPLv2.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/err.h>

#include <mostcore.h>

enum { CH_RX, CH_TX, NUM_CHANNELS };

#define MAX_BUFFERS_CONTROL 32
#define MAX_BUF_SIZE_CONTROL 256

/**
 * list_first_mbo - get the first mbo from a list
 * @ptr:	the list head to take the mbo from.
 */
#define list_first_mbo(ptr) \
	list_first_entry(ptr, struct mbo, list)

/* IRQ / Polling option */
static bool polling_req;
module_param(polling_req, bool, S_IRUGO);
MODULE_PARM_DESC(polling_req, "Request Polling. Default = 0 (use irq)");

/* Polling Rate */
static int scan_rate = 100;
module_param(scan_rate, int, 0644);
MODULE_PARM_DESC(scan_rate, "Polling rate in times/sec. Default = 100");

struct hdm_i2c {
	bool is_open[NUM_CHANNELS];
	bool polling_mode;
	struct most_interface most_iface;
	struct most_channel_capability capabilities[NUM_CHANNELS];
	struct i2c_client *client;
	struct rx {
		struct delayed_work dwork;
		wait_queue_head_t waitq;
		struct list_head list;
		struct mutex list_mutex;
	} rx;
	char name[64];
};

#define to_hdm(iface) container_of(iface, struct hdm_i2c, most_iface)

/**
 * configure_channel - called from MOST core to configure a channel
 * @iface: interface the channel belongs to
 * @channel: channel to be configured
 * @channel_config: structure that holds the configuration information
 *
 * Return 0 on success, negative on failure.
 *
 * Receives configuration information from MOST core and initialize the
 * corresponding channel.
 */
static int configure_channel(struct most_interface *most_iface,
			     int ch_idx,
			     struct most_channel_config *channel_config)
{
	struct hdm_i2c *dev = to_hdm(most_iface);

	BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS);
	BUG_ON(dev->is_open[ch_idx]);

	if (channel_config->data_type != MOST_CH_CONTROL) {
		pr_err("bad data type for channel %d\n", ch_idx);
		return -EPERM;
	}

	if (channel_config->direction != dev->capabilities[ch_idx].direction) {
		pr_err("bad direction for channel %d\n", ch_idx);
		return -EPERM;
	}

	if ((channel_config->direction == MOST_CH_RX) && (dev->polling_mode)) {
		schedule_delayed_work(&dev->rx.dwork,
				      msecs_to_jiffies(MSEC_PER_SEC / 4));
	}
	dev->is_open[ch_idx] = true;

	return 0;
}

/**
 * enqueue - called from MOST core to enqueue a buffer for data transfer
 * @iface: intended interface
 * @channel: ID of the channel the buffer is intended for
 * @mbo: pointer to the buffer object
 *
 * Return 0 on success, negative on failure.
 *
 * Transmit the data over I2C if it is a "write" request or push the buffer into
 * list if it is an "read" request
 */
static int enqueue(struct most_interface *most_iface,
		   int ch_idx, struct mbo *mbo)
{
	struct hdm_i2c *dev = to_hdm(most_iface);
	int ret;

	BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS);
	BUG_ON(!dev->is_open[ch_idx]);

	if (ch_idx == CH_RX) {
		/* RX */
		mutex_lock(&dev->rx.list_mutex);
		list_add_tail(&mbo->list, &dev->rx.list);
		mutex_unlock(&dev->rx.list_mutex);
		wake_up_interruptible(&dev->rx.waitq);
	} else {
		/* TX */
		ret = i2c_master_send(dev->client, mbo->virt_address,
				      mbo->buffer_length);
		if (ret <= 0) {
			mbo->processed_length = 0;
			mbo->status = MBO_E_INVAL;
		} else {
			mbo->processed_length = mbo->buffer_length;
			mbo->status = MBO_SUCCESS;
		}
		mbo->complete(mbo);
	}

	return 0;
}

/**
 * poison_channel - called from MOST core to poison buffers of a channel
 * @iface: pointer to the interface the channel to be poisoned belongs to
 * @channel_id: corresponding channel ID
 *
 * Return 0 on success, negative on failure.
 *
 * If channel direction is RX, complete the buffers in list with
 * status MBO_E_CLOSE
 */
static int poison_channel(struct most_interface *most_iface,
			  int ch_idx)
{
	struct hdm_i2c *dev = to_hdm(most_iface);
	struct mbo *mbo;

	BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS);
	BUG_ON(!dev->is_open[ch_idx]);

	dev->is_open[ch_idx] = false;

	if (ch_idx == CH_RX) {
		mutex_lock(&dev->rx.list_mutex);
		while (!list_empty(&dev->rx.list)) {
			mbo = list_first_mbo(&dev->rx.list);
			list_del(&mbo->list);
			mutex_unlock(&dev->rx.list_mutex);

			mbo->processed_length = 0;
			mbo->status = MBO_E_CLOSE;
			mbo->complete(mbo);

			mutex_lock(&dev->rx.list_mutex);
		}
		mutex_unlock(&dev->rx.list_mutex);
		wake_up_interruptible(&dev->rx.waitq);
	}

	return 0;
}

static void request_netinfo(struct most_interface *most_iface,
			    int ch_idx)
{
	pr_info("request_netinfo()\n");
}

static void do_rx_work(struct hdm_i2c *dev)
{
	struct mbo *mbo;
	unsigned char msg[MAX_BUF_SIZE_CONTROL];
	int ret, ch_idx = CH_RX;
	u16 pml, data_size;

	/* Read PML (2 bytes) */
	ret = i2c_master_recv(dev->client, msg, 2);
	if (ret <= 0) {
		pr_err("Failed to receive PML\n");
		return;
	}

	pml = (msg[0] << 8) | msg[1];
	if (!pml)
		return;

	data_size = pml + 2;

	/* Read the whole message, including PML */
	ret = i2c_master_recv(dev->client, msg, data_size);
	if (ret <= 0) {
		pr_err("Failed to receive a Port Message\n");
		return;
	}

	for (;;) {
		/* Conditions to wait for: poisoned channel or free buffer
		 * available for reading
		 */
		if (wait_event_interruptible(dev->rx.waitq,
					     !dev->is_open[ch_idx] ||
					     !list_empty(&dev->rx.list))) {
			pr_err("wait_event_interruptible() failed\n");
			return;
		}

		if (!dev->is_open[ch_idx])
			return;

		mutex_lock(&dev->rx.list_mutex);

		/* list may be empty if poison or remove is called */
		if (!list_empty(&dev->rx.list))
			break;

		mutex_unlock(&dev->rx.list_mutex);
	}

	mbo = list_first_mbo(&dev->rx.list);
	list_del(&mbo->list);
	mutex_unlock(&dev->rx.list_mutex);

	mbo->processed_length = min(data_size, mbo->buffer_length);
	memcpy(mbo->virt_address, msg, mbo->processed_length);
	mbo->status = MBO_SUCCESS;
	mbo->complete(mbo);
}

/**
 * pending_rx_work - Read pending messages through I2C
 * @work: definition of this work item
 *
 * Invoked by the Interrupt Service Routine, most_irq_handler()
 */
static void pending_rx_work(struct work_struct *work)
{
	struct hdm_i2c *dev = container_of(work, struct hdm_i2c, rx.dwork.work);

	do_rx_work(dev);

	if (dev->polling_mode) {
		if (dev->is_open[CH_RX])
			schedule_delayed_work(&dev->rx.dwork,
					      msecs_to_jiffies(MSEC_PER_SEC
							       / scan_rate));
	} else {
		enable_irq(dev->client->irq);
	}
}

/*
 * most_irq_handler - Interrupt Service Routine
 * @irq: irq number
 * @_dev: private data
 *
 * Schedules a delayed work
 *
 * By default the interrupt line behavior is Active Low. Once an interrupt is
 * generated by the device, until driver clears the interrupt (by reading
 * the PMP message), device keeps the interrupt line in low state. Since i2c
 * read is done in work queue, the interrupt line must be disabled temporarily
 * to avoid ISR being called repeatedly. Re-enable the interrupt in workqueue,
 * after reading the message.
 *
 * Note: If we use the interrupt line in Falling edge mode, there is a
 * possibility to miss interrupts when ISR is getting executed.
 *
 */
static irqreturn_t most_irq_handler(int irq, void *_dev)
{
	struct hdm_i2c *dev = _dev;

	disable_irq_nosync(irq);

	schedule_delayed_work(&dev->rx.dwork, 0);

	return IRQ_HANDLED;
}

/*
 * i2c_probe - i2c probe handler
 * @client: i2c client device structure
 * @id: i2c client device id
 *
 * Return 0 on success, negative on failure.
 *
 * Register the i2c client device as a MOST interface
 */
static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct hdm_i2c *dev;
	int ret, i;
	struct kobject *kobj;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	/* ID format: i2c-<bus>-<address> */
	snprintf(dev->name, sizeof(dev->name), "i2c-%d-%04x",
		 client->adapter->nr, client->addr);

	for (i = 0; i < NUM_CHANNELS; i++) {
		dev->is_open[i] = false;
		dev->capabilities[i].data_type = MOST_CH_CONTROL;
		dev->capabilities[i].num_buffers_packet = MAX_BUFFERS_CONTROL;
		dev->capabilities[i].buffer_size_packet = MAX_BUF_SIZE_CONTROL;
	}
	dev->capabilities[CH_RX].direction = MOST_CH_RX;
	dev->capabilities[CH_RX].name_suffix = "rx";
	dev->capabilities[CH_TX].direction = MOST_CH_TX;
	dev->capabilities[CH_TX].name_suffix = "tx";

	dev->most_iface.interface = ITYPE_I2C;
	dev->most_iface.description = dev->name;
	dev->most_iface.num_channels = NUM_CHANNELS;
	dev->most_iface.channel_vector = dev->capabilities;
	dev->most_iface.configure = configure_channel;
	dev->most_iface.enqueue = enqueue;
	dev->most_iface.poison_channel = poison_channel;
	dev->most_iface.request_netinfo = request_netinfo;

	INIT_LIST_HEAD(&dev->rx.list);
	mutex_init(&dev->rx.list_mutex);
	init_waitqueue_head(&dev->rx.waitq);

	INIT_DELAYED_WORK(&dev->rx.dwork, pending_rx_work);

	dev->client = client;
	i2c_set_clientdata(client, dev);

	kobj = most_register_interface(&dev->most_iface);
	if (IS_ERR(kobj)) {
		pr_err("Failed to register i2c as a MOST interface\n");
		kfree(dev);
		return PTR_ERR(kobj);
	}

	dev->polling_mode = polling_req || client->irq <= 0;
	if (!dev->polling_mode) {
		pr_info("Requesting IRQ: %d\n", client->irq);
		ret = request_irq(client->irq, most_irq_handler, 0,
				  client->name, dev);
		if (ret) {
			pr_info("IRQ request failed: %d, falling back to polling\n",
				ret);
			dev->polling_mode = true;
		}
	}

	if (dev->polling_mode)
		pr_info("Using polling at rate: %d times/sec\n", scan_rate);

	return 0;
}

/*
 * i2c_remove - i2c remove handler
 * @client: i2c client device structure
 *
 * Return 0 on success.
 *
 * Unregister the i2c client device as a MOST interface
 */
static int i2c_remove(struct i2c_client *client)
{
	struct hdm_i2c *dev = i2c_get_clientdata(client);
	int i;

	if (!dev->polling_mode)
		free_irq(client->irq, dev);

	most_deregister_interface(&dev->most_iface);

	for (i = 0 ; i < NUM_CHANNELS; i++)
		if (dev->is_open[i])
			poison_channel(&dev->most_iface, i);
	cancel_delayed_work_sync(&dev->rx.dwork);
	kfree(dev);

	return 0;
}

static const struct i2c_device_id i2c_id[] = {
	{ "most_i2c", 0 },
	{ }, /* Terminating entry */
};

MODULE_DEVICE_TABLE(i2c, i2c_id);

static struct i2c_driver i2c_driver = {
	.driver = {
		.name = "hdm_i2c",
	},
	.probe = i2c_probe,
	.remove = i2c_remove,
	.id_table = i2c_id,
};

module_i2c_driver(i2c_driver);

MODULE_AUTHOR("Jain Roy Ambi <JainRoy.Ambi@microchip.com>");
MODULE_AUTHOR("Andrey Shvetsov <andrey.shvetsov@k2l.de>");
MODULE_DESCRIPTION("I2C Hardware Dependent Module");
MODULE_LICENSE("GPL");