Commit | Line | Data |
---|---|---|
4de6a2d6 TR |
1 | /* |
2 | * Copyright (C) 2013 NVIDIA Corporation | |
3 | * | |
4 | * Permission to use, copy, modify, distribute, and sell this software and its | |
5 | * documentation for any purpose is hereby granted without fee, provided that | |
6 | * the above copyright notice appear in all copies and that both that copyright | |
7 | * notice and this permission notice appear in supporting documentation, and | |
8 | * that the name of the copyright holders not be used in advertising or | |
9 | * publicity pertaining to distribution of the software without specific, | |
10 | * written prior permission. The copyright holders make no representations | |
11 | * about the suitability of this software for any purpose. It is provided "as | |
12 | * is" without express or implied warranty. | |
13 | * | |
14 | * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, | |
15 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO | |
16 | * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR | |
17 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, | |
18 | * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |
19 | * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |
20 | * OF THIS SOFTWARE. | |
21 | */ | |
22 | ||
23 | #include <linux/clk.h> | |
24 | #include <linux/delay.h> | |
aef03d3f | 25 | #include <linux/host1x.h> |
4de6a2d6 TR |
26 | #include <linux/io.h> |
27 | #include <linux/of_platform.h> | |
28 | #include <linux/platform_device.h> | |
29 | #include <linux/slab.h> | |
30 | ||
aef03d3f TR |
31 | #include "dev.h" |
32 | ||
4de6a2d6 TR |
33 | #define MIPI_CAL_CTRL 0x00 |
34 | #define MIPI_CAL_CTRL_START (1 << 0) | |
35 | ||
36 | #define MIPI_CAL_AUTOCAL_CTRL 0x01 | |
37 | ||
38 | #define MIPI_CAL_STATUS 0x02 | |
39 | #define MIPI_CAL_STATUS_DONE (1 << 16) | |
40 | #define MIPI_CAL_STATUS_ACTIVE (1 << 0) | |
41 | ||
42 | #define MIPI_CAL_CONFIG_CSIA 0x05 | |
43 | #define MIPI_CAL_CONFIG_CSIB 0x06 | |
44 | #define MIPI_CAL_CONFIG_CSIC 0x07 | |
45 | #define MIPI_CAL_CONFIG_CSID 0x08 | |
46 | #define MIPI_CAL_CONFIG_CSIE 0x09 | |
47 | #define MIPI_CAL_CONFIG_DSIA 0x0e | |
48 | #define MIPI_CAL_CONFIG_DSIB 0x0f | |
49 | #define MIPI_CAL_CONFIG_DSIC 0x10 | |
50 | #define MIPI_CAL_CONFIG_DSID 0x11 | |
51 | ||
52 | #define MIPI_CAL_CONFIG_SELECT (1 << 21) | |
53 | #define MIPI_CAL_CONFIG_HSPDOS(x) (((x) & 0x1f) << 16) | |
54 | #define MIPI_CAL_CONFIG_HSPUOS(x) (((x) & 0x1f) << 8) | |
55 | #define MIPI_CAL_CONFIG_TERMOS(x) (((x) & 0x1f) << 0) | |
56 | ||
57 | #define MIPI_CAL_BIAS_PAD_CFG0 0x16 | |
58 | #define MIPI_CAL_BIAS_PAD_PDVCLAMP (1 << 1) | |
59 | #define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF (1 << 0) | |
60 | ||
61 | #define MIPI_CAL_BIAS_PAD_CFG1 0x17 | |
62 | ||
63 | #define MIPI_CAL_BIAS_PAD_CFG2 0x18 | |
64 | #define MIPI_CAL_BIAS_PAD_PDVREG (1 << 1) | |
65 | ||
66 | static const struct module { | |
67 | unsigned long reg; | |
68 | } modules[] = { | |
69 | { .reg = MIPI_CAL_CONFIG_CSIA }, | |
70 | { .reg = MIPI_CAL_CONFIG_CSIB }, | |
71 | { .reg = MIPI_CAL_CONFIG_CSIC }, | |
72 | { .reg = MIPI_CAL_CONFIG_CSID }, | |
73 | { .reg = MIPI_CAL_CONFIG_CSIE }, | |
74 | { .reg = MIPI_CAL_CONFIG_DSIA }, | |
75 | { .reg = MIPI_CAL_CONFIG_DSIB }, | |
76 | { .reg = MIPI_CAL_CONFIG_DSIC }, | |
77 | { .reg = MIPI_CAL_CONFIG_DSID }, | |
78 | }; | |
79 | ||
80 | struct tegra_mipi { | |
81 | void __iomem *regs; | |
82 | struct mutex lock; | |
83 | struct clk *clk; | |
84 | }; | |
85 | ||
86 | struct tegra_mipi_device { | |
87 | struct platform_device *pdev; | |
88 | struct tegra_mipi *mipi; | |
89 | struct device *device; | |
90 | unsigned long pads; | |
91 | }; | |
92 | ||
93 | static inline unsigned long tegra_mipi_readl(struct tegra_mipi *mipi, | |
94 | unsigned long reg) | |
95 | { | |
96 | return readl(mipi->regs + (reg << 2)); | |
97 | } | |
98 | ||
99 | static inline void tegra_mipi_writel(struct tegra_mipi *mipi, | |
100 | unsigned long value, unsigned long reg) | |
101 | { | |
102 | writel(value, mipi->regs + (reg << 2)); | |
103 | } | |
104 | ||
105 | struct tegra_mipi_device *tegra_mipi_request(struct device *device) | |
106 | { | |
107 | struct device_node *np = device->of_node; | |
108 | struct tegra_mipi_device *dev; | |
109 | struct of_phandle_args args; | |
110 | int err; | |
111 | ||
112 | err = of_parse_phandle_with_args(np, "nvidia,mipi-calibrate", | |
113 | "#nvidia,mipi-calibrate-cells", 0, | |
114 | &args); | |
115 | if (err < 0) | |
116 | return ERR_PTR(err); | |
117 | ||
118 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
119 | if (!dev) { | |
120 | of_node_put(args.np); | |
121 | err = -ENOMEM; | |
122 | goto out; | |
123 | } | |
124 | ||
125 | dev->pdev = of_find_device_by_node(args.np); | |
126 | if (!dev->pdev) { | |
127 | of_node_put(args.np); | |
128 | err = -ENODEV; | |
129 | goto free; | |
130 | } | |
131 | ||
132 | of_node_put(args.np); | |
133 | ||
134 | dev->mipi = platform_get_drvdata(dev->pdev); | |
135 | if (!dev->mipi) { | |
136 | err = -EPROBE_DEFER; | |
137 | goto pdev_put; | |
138 | } | |
139 | ||
140 | dev->pads = args.args[0]; | |
141 | dev->device = device; | |
142 | ||
143 | return dev; | |
144 | ||
145 | pdev_put: | |
146 | platform_device_put(dev->pdev); | |
147 | free: | |
148 | kfree(dev); | |
149 | out: | |
150 | return ERR_PTR(err); | |
151 | } | |
152 | EXPORT_SYMBOL(tegra_mipi_request); | |
153 | ||
154 | void tegra_mipi_free(struct tegra_mipi_device *device) | |
155 | { | |
156 | platform_device_put(device->pdev); | |
157 | kfree(device); | |
158 | } | |
159 | EXPORT_SYMBOL(tegra_mipi_free); | |
160 | ||
161 | static int tegra_mipi_wait(struct tegra_mipi *mipi) | |
162 | { | |
163 | unsigned long timeout = jiffies + msecs_to_jiffies(250); | |
164 | unsigned long value; | |
165 | ||
166 | while (time_before(jiffies, timeout)) { | |
167 | value = tegra_mipi_readl(mipi, MIPI_CAL_STATUS); | |
168 | if ((value & MIPI_CAL_STATUS_ACTIVE) == 0 && | |
169 | (value & MIPI_CAL_STATUS_DONE) != 0) | |
170 | return 0; | |
171 | ||
172 | usleep_range(10, 50); | |
173 | } | |
174 | ||
175 | return -ETIMEDOUT; | |
176 | } | |
177 | ||
178 | int tegra_mipi_calibrate(struct tegra_mipi_device *device) | |
179 | { | |
180 | unsigned long value; | |
181 | unsigned int i; | |
182 | int err; | |
183 | ||
184 | err = clk_enable(device->mipi->clk); | |
185 | if (err < 0) | |
186 | return err; | |
187 | ||
188 | mutex_lock(&device->mipi->lock); | |
189 | ||
190 | value = tegra_mipi_readl(device->mipi, MIPI_CAL_BIAS_PAD_CFG0); | |
191 | value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP; | |
192 | value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; | |
193 | tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG0); | |
194 | ||
195 | value = tegra_mipi_readl(device->mipi, MIPI_CAL_BIAS_PAD_CFG2); | |
196 | value &= ~MIPI_CAL_BIAS_PAD_PDVREG; | |
197 | tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG2); | |
198 | ||
199 | for (i = 0; i < ARRAY_SIZE(modules); i++) { | |
200 | if (device->pads & BIT(i)) | |
201 | value = MIPI_CAL_CONFIG_SELECT | | |
202 | MIPI_CAL_CONFIG_HSPDOS(0) | | |
203 | MIPI_CAL_CONFIG_HSPUOS(4) | | |
204 | MIPI_CAL_CONFIG_TERMOS(5); | |
205 | else | |
206 | value = 0; | |
207 | ||
208 | tegra_mipi_writel(device->mipi, value, modules[i].reg); | |
209 | } | |
210 | ||
211 | tegra_mipi_writel(device->mipi, MIPI_CAL_CTRL_START, MIPI_CAL_CTRL); | |
212 | ||
213 | err = tegra_mipi_wait(device->mipi); | |
214 | ||
215 | mutex_unlock(&device->mipi->lock); | |
216 | clk_disable(device->mipi->clk); | |
217 | ||
218 | return err; | |
219 | } | |
220 | EXPORT_SYMBOL(tegra_mipi_calibrate); | |
221 | ||
222 | static int tegra_mipi_probe(struct platform_device *pdev) | |
223 | { | |
224 | struct tegra_mipi *mipi; | |
225 | struct resource *res; | |
226 | int err; | |
227 | ||
228 | mipi = devm_kzalloc(&pdev->dev, sizeof(*mipi), GFP_KERNEL); | |
229 | if (!mipi) | |
230 | return -ENOMEM; | |
231 | ||
232 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
233 | mipi->regs = devm_ioremap_resource(&pdev->dev, res); | |
234 | if (IS_ERR(mipi->regs)) | |
235 | return PTR_ERR(mipi->regs); | |
236 | ||
237 | mutex_init(&mipi->lock); | |
238 | ||
239 | mipi->clk = devm_clk_get(&pdev->dev, NULL); | |
240 | if (IS_ERR(mipi->clk)) { | |
241 | dev_err(&pdev->dev, "failed to get clock\n"); | |
242 | return PTR_ERR(mipi->clk); | |
243 | } | |
244 | ||
245 | err = clk_prepare(mipi->clk); | |
246 | if (err < 0) | |
247 | return err; | |
248 | ||
249 | platform_set_drvdata(pdev, mipi); | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | static int tegra_mipi_remove(struct platform_device *pdev) | |
255 | { | |
256 | struct tegra_mipi *mipi = platform_get_drvdata(pdev); | |
257 | ||
258 | clk_unprepare(mipi->clk); | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | static struct of_device_id tegra_mipi_of_match[] = { | |
264 | { .compatible = "nvidia,tegra114-mipi", }, | |
265 | { }, | |
266 | }; | |
267 | ||
268 | struct platform_driver tegra_mipi_driver = { | |
269 | .driver = { | |
270 | .name = "tegra-mipi", | |
271 | .of_match_table = tegra_mipi_of_match, | |
272 | }, | |
273 | .probe = tegra_mipi_probe, | |
274 | .remove = tegra_mipi_remove, | |
275 | }; |