summaryrefslogtreecommitdiff
path: root/drivers/w1/mxc_w1.c
blob: 9279ba32b85478ce9b7df5c6b3b6cf89005e41c5 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Driver for one wire controller in some i.MX Socs
 *
 * There are currently two silicon variants:
 * V1: i.MX21, i.MX27, i.MX31, i.MX51
 * V2: i.MX25, i.MX35, i.MX50, i.MX53
 * Newer i.MX SoCs such as the i.MX6 do not have one wire controllers.
 *
 * The V1 controller only supports single bit operations.
 * The V2 controller is backwards compatible on the register level but adds
 * byte size operations and a "search ROM accelerator mode"
 *
 * This driver does not currently support the search ROM accelerator
 *
 * Copyright (c) 2018 Flowbird
 * Martin Fuzzey <martin.fuzzey@flowbird.group>
 */

#include <asm/arch/clock.h>
#include <common.h>
#include <dm.h>
#include <linux/io.h>
#include <w1.h>

struct mxc_w1_regs {
	u16 control;
#define MXC_W1_CONTROL_RPP	BIT(7)
#define MXC_W1_CONTROL_PST	BIT(6)
#define MXC_W1_CONTROL_WR(x)	BIT(5 - (x))
#define MXC_W1_CONTROL_RDST	BIT(3)

	u16 time_divider;
	u16 reset;

	/* Registers below on V2 silicon only */
	u16 command;
	u16 tx_rx;
	u16 interrupt;
#define MXC_W1_INTERRUPT_TBE	BIT(2)
#define MXC_W1_INTERRUPT_TSRE	BIT(3)
#define MXC_W1_INTERRUPT_RBF	BIT(4)
#define MXC_W1_INTERRUPT_RSRF	BIT(5)

	u16 interrupt_en;
};

struct mxc_w1_pdata {
	struct mxc_w1_regs *regs;
};

/*
 * this is the low level routine to read/write a bit on the One Wire
 * interface on the hardware. It does write 0 if parameter bit is set
 * to 0, otherwise a write 1/read.
 */
static u8 mxc_w1_touch_bit(struct mxc_w1_pdata *pdata, u8 bit)
{
	u16 *ctrl_addr = &pdata->regs->control;
	u16 mask = MXC_W1_CONTROL_WR(bit);
	unsigned int timeout_cnt = 400; /* Takes max. 120us according to
					 * datasheet.
					 */

	writew(mask, ctrl_addr);

	while (timeout_cnt--) {
		if (!(readw(ctrl_addr) & mask))
			break;

		udelay(1);
	}

	return (readw(ctrl_addr) & MXC_W1_CONTROL_RDST) ? 1 : 0;
}

static u8 mxc_w1_read_byte(struct udevice *dev)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	struct mxc_w1_regs *regs = pdata->regs;
	u16 status;

	if (dev_get_driver_data(dev) < 2) {
		int i;
		u8 ret = 0;

		for (i = 0; i < 8; i++)
			ret |= (mxc_w1_touch_bit(pdata, 1) << i);

		return ret;
	}

	readw(&regs->tx_rx);
	writew(0xFF, &regs->tx_rx);

	do {
		udelay(1); /* Without this bytes are sometimes duplicated... */
		status = readw(&regs->interrupt);
	} while (!(status & MXC_W1_INTERRUPT_RBF));

	return (u8)readw(&regs->tx_rx);
}

static void mxc_w1_write_byte(struct udevice *dev, u8 byte)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	struct mxc_w1_regs *regs = pdata->regs;
	u16 status;

	if (dev_get_driver_data(dev) < 2) {
		int i;

		for (i = 0; i < 8; i++)
			mxc_w1_touch_bit(pdata, (byte >> i) & 0x1);

		return;
	}

	readw(&regs->tx_rx);
	writew(byte, &regs->tx_rx);

	do {
		udelay(1);
		status = readw(&regs->interrupt);
	} while (!(status & MXC_W1_INTERRUPT_TSRE));
}

static bool mxc_w1_reset(struct udevice *dev)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	u16 reg_val;

	writew(MXC_W1_CONTROL_RPP, &pdata->regs->control);

	do {
		reg_val = readw(&pdata->regs->control);
	}  while (reg_val & MXC_W1_CONTROL_RPP);

	return !(reg_val & MXC_W1_CONTROL_PST);
}

static u8 mxc_w1_triplet(struct udevice *dev, bool bdir)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	u8 id_bit   = mxc_w1_touch_bit(pdata, 1);
	u8 comp_bit = mxc_w1_touch_bit(pdata, 1);
	u8 retval;

	if (id_bit && comp_bit)
		return 0x03;  /* error */

	if (!id_bit && !comp_bit) {
		/* Both bits are valid, take the direction given */
		retval = bdir ? 0x04 : 0;
	} else {
		/* Only one bit is valid, take that direction */
		bdir = id_bit;
		retval = id_bit ? 0x05 : 0x02;
	}

	mxc_w1_touch_bit(pdata, bdir);

	return retval;
}

static int mxc_w1_ofdata_to_platdata(struct udevice *dev)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	fdt_addr_t addr;

	addr = devfdt_get_addr(dev);
	if (addr == FDT_ADDR_T_NONE)
		return -EINVAL;

	pdata->regs = (struct mxc_w1_regs *)addr;

	return 0;
};

static int mxc_w1_probe(struct udevice *dev)
{
	struct mxc_w1_pdata *pdata = dev_get_platdata(dev);
	unsigned int clkrate = mxc_get_clock(MXC_IPG_PERCLK);
	unsigned int clkdiv;

	if (clkrate < 10000000) {
		dev_err(dev, "input clock frequency (%u Hz) too low\n",
			clkrate);
		return -EINVAL;
	}

	clkdiv = clkrate / 1000000;
	clkrate /= clkdiv;
	if (clkrate < 980000 || clkrate > 1020000) {
		dev_err(dev, "Incorrect time base frequency %u Hz\n", clkrate);
		return -EINVAL;
	}

	writew(clkdiv - 1, &pdata->regs->time_divider);

	return 0;
}

static const struct w1_ops mxc_w1_ops = {
	.read_byte	= mxc_w1_read_byte,
	.reset		= mxc_w1_reset,
	.triplet	= mxc_w1_triplet,
	.write_byte	= mxc_w1_write_byte,
};

static const struct udevice_id mxc_w1_id[] = {
	{ .compatible = "fsl,imx21-owire", .data = 1 },
	{ .compatible = "fsl,imx27-owire", .data = 1 },
	{ .compatible = "fsl,imx31-owire", .data = 1 },
	{ .compatible = "fsl,imx51-owire", .data = 1 },

	{ .compatible = "fsl,imx25-owire", .data = 2 },
	{ .compatible = "fsl,imx35-owire", .data = 2 },
	{ .compatible = "fsl,imx50-owire", .data = 2 },
	{ .compatible = "fsl,imx53-owire", .data = 2 },
	{ },
};

U_BOOT_DRIVER(mxc_w1_drv) = {
	.id				= UCLASS_W1,
	.name				= "mxc_w1_drv",
	.of_match			= mxc_w1_id,
	.ofdata_to_platdata		= mxc_w1_ofdata_to_platdata,
	.ops				= &mxc_w1_ops,
	.platdata_auto_alloc_size	= sizeof(struct mxc_w1_pdata),
	.probe				= mxc_w1_probe,
};