summaryrefslogtreecommitdiff
path: root/drivers/i2c/sun6i_p2wi.c
blob: c9e1b3fcd5f7059bb44fffec83a70c271a065d4b (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Sunxi A31 Power Management Unit
 *
 * (C) Copyright 2013 Oliver Schinagl <oliver@schinagl.nl>
 * http://linux-sunxi.org
 *
 * Based on sun6i sources and earlier U-Boot Allwinner A10 SPL work
 *
 * (C) Copyright 2006-2013
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 * Berg Xing <bergxing@allwinnertech.com>
 * Tom Cubie <tangliang@allwinnertech.com>
 */

#include <axp_pmic.h>
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <i2c.h>
#include <time.h>
#include <asm/io.h>
#include <asm/arch/cpu.h>
#include <asm/arch/gpio.h>
#include <asm/arch/p2wi.h>
#include <asm/arch/prcm.h>
#include <asm/arch/sys_proto.h>

static int sun6i_p2wi_await_trans(struct sunxi_p2wi_reg *base)
{
	unsigned long tmo = timer_get_us() + 1000000;
	int ret;
	u8 reg;

	while (1) {
		reg = readl(&base->status);
		if (reg & P2WI_STAT_TRANS_ERR) {
			ret = -EIO;
			break;
		}
		if (reg & P2WI_STAT_TRANS_DONE) {
			ret = 0;
			break;
		}
		if (timer_get_us() > tmo) {
			ret = -ETIME;
			break;
		}
	}
	writel(reg, &base->status); /* Clear status bits */

	return ret;
}

static int sun6i_p2wi_read(struct sunxi_p2wi_reg *base, const u8 addr, u8 *data)
{
	int ret;

	writel(P2WI_DATADDR_BYTE_1(addr), &base->dataddr0);
	writel(P2WI_DATA_NUM_BYTES(1) |
	       P2WI_DATA_NUM_BYTES_READ, &base->numbytes);
	writel(P2WI_STAT_TRANS_DONE, &base->status);
	writel(P2WI_CTRL_TRANS_START, &base->ctrl);

	ret = sun6i_p2wi_await_trans(base);

	*data = readl(&base->data0) & P2WI_DATA_BYTE_1_MASK;

	return ret;
}

static int sun6i_p2wi_write(struct sunxi_p2wi_reg *base, const u8 addr, u8 data)
{
	writel(P2WI_DATADDR_BYTE_1(addr), &base->dataddr0);
	writel(P2WI_DATA_BYTE_1(data), &base->data0);
	writel(P2WI_DATA_NUM_BYTES(1), &base->numbytes);
	writel(P2WI_STAT_TRANS_DONE, &base->status);
	writel(P2WI_CTRL_TRANS_START, &base->ctrl);

	return sun6i_p2wi_await_trans(base);
}

static int sun6i_p2wi_change_to_p2wi_mode(struct sunxi_p2wi_reg *base,
					  u8 slave_addr, u8 ctrl_reg,
					  u8 init_data)
{
	unsigned long tmo = timer_get_us() + 1000000;

	writel(P2WI_PM_DEV_ADDR(slave_addr) |
	       P2WI_PM_CTRL_ADDR(ctrl_reg) |
	       P2WI_PM_INIT_DATA(init_data) |
	       P2WI_PM_INIT_SEND,
	       &base->pm);

	while ((readl(&base->pm) & P2WI_PM_INIT_SEND)) {
		if (timer_get_us() > tmo)
			return -ETIME;
	}

	return 0;
}

static void sun6i_p2wi_init(struct sunxi_p2wi_reg *base)
{
	/* Enable p2wi and PIO clk, and de-assert their resets */
	prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_P2WI);

	sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN6I_GPL0_R_P2WI_SCK);
	sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN6I_GPL1_R_P2WI_SDA);

	/* Reset p2wi controller and set clock to CLKIN(12)/8 = 1.5 MHz */
	writel(P2WI_CTRL_RESET, &base->ctrl);
	sdelay(0x100);
	writel(P2WI_CC_SDA_OUT_DELAY(1) | P2WI_CC_CLK_DIV(8),
	       &base->cc);
}

#if IS_ENABLED(CONFIG_AXP_PMIC_BUS)
int p2wi_read(const u8 addr, u8 *data)
{
	struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE;

	return sun6i_p2wi_read(base, addr, data);
}

int p2wi_write(const u8 addr, u8 data)
{
	struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE;

	return sun6i_p2wi_write(base, addr, data);
}

int p2wi_change_to_p2wi_mode(u8 slave_addr, u8 ctrl_reg, u8 init_data)
{
	struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE;

	return sun6i_p2wi_change_to_p2wi_mode(base, slave_addr, ctrl_reg,
					      init_data);
}

void p2wi_init(void)
{
	struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE;

	sun6i_p2wi_init(base);
}
#endif

#if CONFIG_IS_ENABLED(DM_I2C)
struct sun6i_p2wi_priv {
	struct sunxi_p2wi_reg *base;
};

static int sun6i_p2wi_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs)
{
	struct sun6i_p2wi_priv *priv = dev_get_priv(bus);

	/* The hardware only supports SMBus-style transfers. */
	if (nmsgs == 2 && msg[1].flags == I2C_M_RD && msg[1].len == 1)
		return sun6i_p2wi_read(priv->base,
				       msg[0].buf[0], &msg[1].buf[0]);

	if (nmsgs == 1 && msg[0].len == 2)
		return sun6i_p2wi_write(priv->base,
					msg[0].buf[0], msg[0].buf[1]);

	return -EINVAL;
}

static int sun6i_p2wi_probe_chip(struct udevice *bus, uint chip_addr,
				 uint chip_flags)
{
	struct sun6i_p2wi_priv *priv = dev_get_priv(bus);

	return sun6i_p2wi_change_to_p2wi_mode(priv->base, chip_addr,
					      AXP_PMIC_MODE_REG,
					      AXP_PMIC_MODE_P2WI);
}

static int sun6i_p2wi_probe(struct udevice *bus)
{
	struct sun6i_p2wi_priv *priv = dev_get_priv(bus);

	priv->base = dev_read_addr_ptr(bus);

	sun6i_p2wi_init(priv->base);

	return 0;
}

static int sun6i_p2wi_child_pre_probe(struct udevice *child)
{
	struct dm_i2c_chip *chip = dev_get_parent_plat(child);

	/* Ensure each transfer is for a single register. */
	chip->flags |= DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS;

	return 0;
}

static const struct dm_i2c_ops sun6i_p2wi_ops = {
	.xfer		= sun6i_p2wi_xfer,
	.probe_chip	= sun6i_p2wi_probe_chip,
};

static const struct udevice_id sun6i_p2wi_ids[] = {
	{ .compatible = "allwinner,sun6i-a31-p2wi" },
	{ /* sentinel */ }
};

U_BOOT_DRIVER(sun6i_p2wi) = {
	.name			= "sun6i_p2wi",
	.id			= UCLASS_I2C,
	.of_match		= sun6i_p2wi_ids,
	.probe			= sun6i_p2wi_probe,
	.child_pre_probe	= sun6i_p2wi_child_pre_probe,
	.priv_auto		= sizeof(struct sun6i_p2wi_priv),
	.ops			= &sun6i_p2wi_ops,
};
#endif /* CONFIG_IS_ENABLED(DM_I2C) */