summaryrefslogtreecommitdiff
path: root/drivers/w1/masters/tegra_w1.c
blob: 9443c4b1dbc65ad46e3625524f01dcba5a290f56 (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
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
/*
 * drivers/w1/masters/tegra-w1.c
 *
 * W1 master driver for internal OWR controllers in NVIDIA Tegra SoCs.
 *
 * Copyright (C) 2010 Motorola, Inc
 * Author: Andrei Warkentin <andreiw@motorola.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/uaccess.h>

#include <mach/w1.h>

#include "../w1.h"
#include "../w1_int.h"
#include "../w1_log.h"

#define DRIVER_NAME "tegra_w1"

/* OWR_CONTROL_0 is the main control register, and should be configured
   last after configuring all other settings. */
#define OWR_CONTROL          (0x0)
#define OC_RD_BIT            (1 << 31)
#define OC_WR0_BIT           (1 << 30)
#define OC_RD_SCLK_SHIFT     (23)
#define OC_RD_SCLK_MASK      (0xF)
#define OC_P_SCLK_SHIFT      (15)
#define OC_P_SCLK_MASK       (0xFF)
#define OC_BIT_XMODE         (1 << 2)
#define OC_GO                (1 << 0)

/* OWR_WR_RD_TCTL_0 controls read/write timings. */
#define OWR_WR_RD_TCTL       (0xc)
#define ORWT_TSU_SHIFT       (28)
#define ORWT_TSU_MASK        (0x3)
#define ORWT_TRELEASE_SHIFT  (22)
#define ORWT_TRELEASE_MASK   (0x3F)
#define ORWT_TRDV_SHIFT      (18)
#define ORWT_TRDV_MASK       (0xF)
#define ORWT_TLOW0_SHIFT     (11)
#define ORWT_TLOW0_MASK      (0x7F)
#define ORWT_TLOW1_SHIFT     (7)
#define ORWT_TLOW1_MASK      (0xF)
#define ORWT_TSLOT_SHIFT     (0)
#define ORWT_TSLOT_MASK      (0x7F)

/* OWR_RST_PRES_TCTL_0 controls reset presence timings. */
#define OWR_RST_PRES_TCTL    (0x10)
#define ORPT_TPDL_SHIFT      (24)
#define ORPT_TPDL_MASK       (0xFF)
#define ORPT_TPDH_SHIFT      (18)
#define ORPT_TPDH_MASK       (0x3F)
#define ORPT_TRSTL_SHIFT     (9)
#define ORPT_TRSTL_MASK      (0x1FF)
#define ORPT_TRSTH_SHIFT     (0)
#define ORPT_TRSTH_MASK      (0x1FF)

/* OWR_INTR_MASK_0 stores the masks for the interrupts. */
#define OWR_INTR_MASK         (0x24)
#define OI_BIT_XFER_DONE      (1 << 13)
#define OI_PRESENCE_DONE      (1 << 5)
#define OI_PRESENCE_ERR       (1 << 0)

/* OWR_INTR_STATUS_0 is the interrupt status register. */
#define OWR_INTR_STATUS       (0x28)

/* OWR_STATUS_0 is the status register. */
#define OWR_STATUS            (0x34)
#define OS_READ_BIT_SHIFT     (23)
#define OS_RDY                (1 << 0)

/* Transfer_completion wait time. */
#define BIT_XFER_COMPLETION_TIMEOUT_MSEC (5000)

/* Errors in the interrupt status register for bit
   transfers. */
#define BIT_XFER_ERRORS (OI_PRESENCE_ERR)

/* OWR requires 1MHz clock. This value is in Herz. */
#define OWR_CLOCK (1000000)

#define W1_ERR(format, ...) \
	printk(KERN_ERR "(%s: line %d) " format, \
	__func__, __LINE__, ## __VA_ARGS__)

struct tegra_device {
	bool ready;
	struct w1_bus_master bus_master;
	struct clk *clk;
	void __iomem *ioaddr;
	struct mutex mutex;
	spinlock_t spinlock;
	struct completion *transfer_completion;
	unsigned long intr_status;
	struct tegra_w1_timings *timings;
};

/* If debug_print & DEBUG_PRESENCE, print whether slaves detected
   or not in reset_bus. */
#define DEBUG_PRESENCE (0x1)

/* If debug_print & DEBUG_TIMEOUT, print whether timeouts on waiting
   for device interrupts occurs. */
#define DEBUG_TIMEOUT (0x2)

static uint32_t debug_print;
module_param_named(debug, debug_print, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debugging output commands:\n"
		 "\tbit 0 - log reset_bus presence detects\n"
		 "\tbit 1 - log interrupt timeouts\n");

/* Reads the OWR register specified by base offset in 'reg'. */
static inline unsigned long w1_readl(struct tegra_device *dev,
				     unsigned long reg)
{
	return readl(dev->ioaddr + reg);
}

/* Writes 'val' into the OWR registers specified by base offset in 'reg'. */
static inline void w1_writel(struct tegra_device *dev, unsigned long val,
			     unsigned long reg)
{
	writel(val, dev->ioaddr + reg);
}

/* Sets interrupt mask the device. */
static inline void w1_imask(struct tegra_device *dev, unsigned long mask)
{
	w1_writel(dev, mask, OWR_INTR_MASK);
}

/* Waits for completion of a bit transfer, checks intr_status against
   BIT_XFER_ERRORS and an additional provided bit mask. */
static inline int w1_wait(struct tegra_device *dev, unsigned long mask)
{
	int ret;
	unsigned long irq_flags;
	unsigned long intr_status;

	ret = wait_for_completion_timeout(dev->transfer_completion,
		msecs_to_jiffies(BIT_XFER_COMPLETION_TIMEOUT_MSEC));

	if (unlikely(!ret)) {
		if (debug_print & DEBUG_TIMEOUT)
			W1_ERR("timeout\n");
		return -ETIME;
	}

	spin_lock_irqsave(&dev->spinlock, irq_flags);
	intr_status = dev->intr_status;
	dev->intr_status = 0;
	spin_unlock_irqrestore(&dev->spinlock, irq_flags);

	if (unlikely(intr_status & BIT_XFER_ERRORS ||
		     !(intr_status & mask)))
		return -EIO;
	return 0;
}

/* Programs timing registers, and puts the device into a known state.
   Interrupts are safe to enable past this point. */
static int w1_setup(struct tegra_device *dev)
{
	unsigned long value;
	clk_enable(dev->clk);

	value =
		((dev->timings->tslot & ORWT_TSLOT_MASK) << ORWT_TSLOT_SHIFT) |
		((dev->timings->tlow1 & ORWT_TLOW1_MASK) << ORWT_TLOW1_SHIFT) |
		((dev->timings->tlow0 & ORWT_TLOW0_MASK) << ORWT_TLOW0_SHIFT) |
		((dev->timings->trdv & ORWT_TRDV_MASK) << ORWT_TRDV_SHIFT) |
		((dev->timings->trelease & ORWT_TRELEASE_MASK) <<
		 ORWT_TRELEASE_SHIFT) |
		((dev->timings->tsu & ORWT_TSU_MASK) << ORWT_TSU_SHIFT);
	w1_writel(dev, value, OWR_WR_RD_TCTL);

	value =
		((dev->timings->trsth & ORPT_TRSTH_MASK) << ORPT_TRSTH_SHIFT) |
		((dev->timings->trstl & ORPT_TRSTL_MASK) << ORPT_TRSTL_SHIFT) |
		((dev->timings->tpdh & ORPT_TPDH_MASK) << ORPT_TPDH_SHIFT) |
		((dev->timings->tpdl & ORPT_TPDL_MASK) << ORPT_TPDL_SHIFT);
	w1_writel(dev, value, OWR_RST_PRES_TCTL);

	/* Clear interrupt status/mask registers in case
	   anything was set in it. */
	w1_imask(dev, 0);
	w1_writel(dev, 0xFFFFFFFF, OWR_INTR_STATUS);
	clk_disable(dev->clk);
	return 0;
}

/* Interrupt handler for OWR communication. */
static irqreturn_t tegra_w1_irq(int irq, void *cookie)
{
	unsigned long irq_flags;
	unsigned long status;
	struct tegra_device *dev = cookie;

	status = w1_readl(dev, OWR_INTR_STATUS);
	if (unlikely(!status)) {

		/* Not for me if no status bits are set. */
		return IRQ_NONE;
	}

	spin_lock_irqsave(&dev->spinlock, irq_flags);

	if (likely(dev->transfer_completion)) {
		dev->intr_status = status;
		w1_writel(dev, status, OWR_INTR_STATUS);
		complete(dev->transfer_completion);
	} else {
		W1_ERR("spurious interrupt, status = 0x%lx\n", status);
	}

	spin_unlock_irqrestore(&dev->spinlock, irq_flags);
	return IRQ_HANDLED;
}

/* Perform a write-0 cycle if bit == 0, otherwise
   perform a read cycle. */
static u8 tegra_w1_touch_bit(void *data, u8 bit)
{
	int rc;
	u8 return_bit;
	unsigned long control;
	DECLARE_COMPLETION_ONSTACK(touch_done);
	struct tegra_device *dev = (struct tegra_device *) data;

	return_bit = 0;
	mutex_lock(&dev->mutex);
	if (!dev->ready)
		goto done;

	clk_enable(dev->clk);
	w1_imask(dev, OI_BIT_XFER_DONE);
	dev->transfer_completion = &touch_done;
	control =
		((dev->timings->rdsclk & OC_RD_SCLK_MASK) << OC_RD_SCLK_SHIFT) |
		((dev->timings->psclk & OC_P_SCLK_MASK) << OC_P_SCLK_SHIFT) |
		OC_BIT_XMODE;

	/* Read bit (well, writes a 1 to the bus as well). */
	if (bit) {
		w1_writel(dev, control | OC_RD_BIT, OWR_CONTROL);
		rc = w1_wait(dev, OI_BIT_XFER_DONE);

		if (rc) {
			W1_ERR("write-1/read failed\n");
			goto done;
		}

		return_bit =
		    (w1_readl(dev, OWR_STATUS) >> OS_READ_BIT_SHIFT) & 1;

	}

	/* Write 0. */
	else {
		w1_writel(dev, control | OC_WR0_BIT, OWR_CONTROL);
		rc = w1_wait(dev, OI_BIT_XFER_DONE);
		if (rc) {
			W1_ERR("write-0 failed\n");
			goto done;
		}
	}

done:

	w1_imask(dev, 0);
	dev->transfer_completion = NULL;
	clk_disable(dev->clk);
	mutex_unlock(&dev->mutex);
	return return_bit;
}

/* Performs a bus reset cycle, and returns 0 if slaves present. */
static u8 tegra_w1_reset_bus(void *data)
{
	int rc;
	int presence;
	unsigned long value;
	DECLARE_COMPLETION_ONSTACK(reset_done);
	struct tegra_device *dev = (struct tegra_device *) data;

	presence = 1;
	mutex_lock(&dev->mutex);
	if (!dev->ready)
		goto done;

	clk_enable(dev->clk);
	w1_imask(dev, OI_PRESENCE_DONE);
	dev->transfer_completion = &reset_done;
	value =
	    ((dev->timings->rdsclk & OC_RD_SCLK_MASK) << OC_RD_SCLK_SHIFT) |
	    ((dev->timings->psclk & OC_P_SCLK_MASK) << OC_P_SCLK_SHIFT) |
	    OC_BIT_XMODE | OC_GO;
	w1_writel(dev, value, OWR_CONTROL);

	rc = w1_wait(dev, OI_PRESENCE_DONE);
	if (rc)
		goto done;

	presence = 0;
done:

	if (debug_print & DEBUG_PRESENCE) {
		if (presence)
			W1_ERR("no slaves present\n");
		else
			W1_ERR("slaves present\n");
	}

	w1_imask(dev, 0);
	dev->transfer_completion = NULL;
	clk_disable(dev->clk);
	mutex_unlock(&dev->mutex);
	return presence;
}

static int tegra_w1_probe(struct platform_device *pdev)
{
	int rc;
	int irq;
	struct resource *res;
	struct tegra_device *dev;
	struct tegra_w1_platform_data *plat = pdev->dev.platform_data;

	printk(KERN_INFO "Driver for Tegra SoC 1-wire controller\n");

	if (plat == NULL || plat->timings == NULL)
		return -ENXIO;

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res == NULL)
		return -ENODEV;

	irq = res->start;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL)
		return -ENODEV;

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

	platform_set_drvdata(pdev, dev);
	dev->clk = clk_get(&pdev->dev, plat->clk_id);
	if (IS_ERR(dev->clk)) {
		rc = PTR_ERR(dev->clk);
		goto cleanup_alloc;
	}

	/* OWR requires 1MHz clock. */
	rc = clk_set_rate(dev->clk, OWR_CLOCK);
	if (rc)
		goto cleanup_clock;

	if (!request_mem_region
	    (res->start, res->end - res->start + 1, dev_name(&pdev->dev))) {
		rc = -EBUSY;
		goto cleanup_clock;
	}

	dev->ioaddr = ioremap(res->start, res->end - res->start + 1);
	if (!dev->ioaddr) {
		rc = -ENOMEM;
		goto cleanup_reqmem;
	}

	dev->timings = plat->timings;
	dev->bus_master.data = dev;
	dev->bus_master.touch_bit = tegra_w1_touch_bit;
	dev->bus_master.reset_bus = tegra_w1_reset_bus;

	spin_lock_init(&dev->spinlock);
	mutex_init(&dev->mutex);

	/* Program device into known state. */
	w1_setup(dev);

	rc = request_irq(irq, tegra_w1_irq, IRQF_SHARED, DRIVER_NAME, dev);
	if (rc)
		goto cleanup_ioremap;

	rc = w1_add_master_device(&dev->bus_master);
	if (rc)
		goto cleanup_irq;

	dev->ready = true;
	return 0;

cleanup_irq:
	free_irq(irq, dev);
cleanup_ioremap:
	iounmap(dev->ioaddr);
cleanup_reqmem:
	release_mem_region(res->start,
			   res->end - res->start + 1);
cleanup_clock:
	clk_put(dev->clk);
cleanup_alloc:
	platform_set_drvdata(pdev, NULL);
	kfree(dev);
	return rc;
}

static int tegra_w1_remove(struct platform_device *pdev)
{
	struct resource *res;
	struct tegra_device *dev = platform_get_drvdata(pdev);

	mutex_lock(&dev->mutex);
	dev->ready = false;
	mutex_unlock(&dev->mutex);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	free_irq(res->start, dev);
	iounmap(dev->ioaddr);
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	release_mem_region(res->start, res->end - res->start + 1);
	clk_put(dev->clk);
	platform_set_drvdata(pdev, NULL);
	kfree(dev);
	return 0;
}

static int tegra_w1_suspend(struct platform_device *pdev, pm_message_t state)
{
	return 0;
}

static int tegra_w1_resume(struct platform_device *pdev)
{
	struct tegra_device *dev = platform_get_drvdata(pdev);

	/* TODO: Is this necessary? I would assume yes. */
	w1_setup(dev);
	return 0;
}

static struct platform_driver tegra_w1_driver = {
	.probe = tegra_w1_probe,
	.remove = tegra_w1_remove,
	.suspend = tegra_w1_suspend,
	.resume = tegra_w1_resume,
	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
	},
};

static int __init tegra_w1_init(void)
{
	return platform_driver_register(&tegra_w1_driver);
}

static void __exit tegra_w1_exit(void)
{
	platform_driver_unregister(&tegra_w1_driver);
}

module_init(tegra_w1_init);
module_exit(tegra_w1_exit);

MODULE_DESCRIPTION("Tegra W1 master driver");
MODULE_AUTHOR("Andrei Warkentin <andreiw@motorola.com>");
MODULE_LICENSE("GPL");