blob: a85d13cdebd1b816feac197f425b939189a41c7d [file] [log] [blame]
Stephen Boyd67036532012-01-26 15:43:51 -08001/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
Stephen Boyd4eb885b2011-09-29 01:16:03 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/init.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/io.h>
17#include <linux/ioport.h>
18#include <linux/elf.h>
19#include <linux/delay.h>
20#include <linux/err.h>
Stephen Boyd67036532012-01-26 15:43:51 -080021#include <linux/workqueue.h>
22#include <linux/clk.h>
Stephen Boyd4eb885b2011-09-29 01:16:03 -070023
24#include <mach/msm_iomap.h>
Stephen Boyd4eb885b2011-09-29 01:16:03 -070025
26#include "peripheral-loader.h"
27#include "scm-pas.h"
28
29#define MARM_BOOT_CONTROL 0x0010
30#define MARM_RESET (MSM_CLK_CTL_BASE + 0x2BD4)
31#define MAHB0_SFAB_PORT_RESET (MSM_CLK_CTL_BASE + 0x2304)
32#define MARM_CLK_BRANCH_ENA_VOTE (MSM_CLK_CTL_BASE + 0x3000)
33#define MARM_CLK_SRC0_NS (MSM_CLK_CTL_BASE + 0x2BC0)
34#define MARM_CLK_SRC1_NS (MSM_CLK_CTL_BASE + 0x2BC4)
35#define MARM_CLK_SRC_CTL (MSM_CLK_CTL_BASE + 0x2BC8)
36#define MARM_CLK_CTL (MSM_CLK_CTL_BASE + 0x2BCC)
37#define SFAB_MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C00)
38#define MSS_MODEM_CXO_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C44)
39#define MSS_SLP_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C60)
40#define MSS_MARM_SYS_REF_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C64)
41#define MAHB0_CLK_CTL (MSM_CLK_CTL_BASE + 0x2300)
42#define MAHB1_CLK_CTL (MSM_CLK_CTL_BASE + 0x2BE4)
43#define MAHB2_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C20)
44#define MAHB1_NS (MSM_CLK_CTL_BASE + 0x2BE0)
45#define MARM_CLK_FS (MSM_CLK_CTL_BASE + 0x2BD0)
46#define MAHB2_CLK_FS (MSM_CLK_CTL_BASE + 0x2C24)
47#define PLL_ENA_MARM (MSM_CLK_CTL_BASE + 0x3500)
48#define PLL8_STATUS (MSM_CLK_CTL_BASE + 0x3158)
49#define CLK_HALT_MSS_SMPSS_MISC_STATE (MSM_CLK_CTL_BASE + 0x2FDC)
50
51#define PROXY_VOTE_TIMEOUT 10000
52
53struct modem_data {
54 void __iomem *base;
55 unsigned long start_addr;
Stephen Boyd6d67d252011-09-27 11:50:05 -070056 struct pil_device *pil;
Stephen Boyd67036532012-01-26 15:43:51 -080057 struct clk *xo;
58 struct delayed_work work;
Stephen Boyd4eb885b2011-09-29 01:16:03 -070059};
60
61static int nop_verify_blob(struct pil_desc *pil, u32 phy_addr, size_t size)
62{
63 return 0;
64}
65
Stephen Boyd67036532012-01-26 15:43:51 -080066static int make_modem_proxy_votes(struct device *dev)
Stephen Boyd4eb885b2011-09-29 01:16:03 -070067{
68 int ret;
69 struct modem_data *drv = dev_get_drvdata(dev);
70
Stephen Boyd67036532012-01-26 15:43:51 -080071 ret = clk_prepare_enable(drv->xo);
72 if (ret) {
73 dev_err(dev, "Failed to enable XO\n");
74 return ret;
75 }
76 schedule_delayed_work(&drv->work, msecs_to_jiffies(PROXY_VOTE_TIMEOUT));
77 return 0;
78}
79
80static void remove_modem_proxy_votes(struct work_struct *work)
81{
82 struct modem_data *drv;
83 drv = container_of(work, struct modem_data, work.work);
84 clk_disable_unprepare(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -070085}
86
87static void remove_modem_proxy_votes_now(struct modem_data *drv)
88{
Stephen Boyd67036532012-01-26 15:43:51 -080089 flush_delayed_work(&drv->work);
Stephen Boyd4eb885b2011-09-29 01:16:03 -070090}
91
92static int modem_init_image(struct pil_desc *pil, const u8 *metadata,
93 size_t size)
94{
95 const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata;
96 struct modem_data *drv = dev_get_drvdata(pil->dev);
97 drv->start_addr = ehdr->e_entry;
98 return 0;
99}
100
101static int modem_reset(struct pil_desc *pil)
102{
103 u32 reg;
Stephen Boyd67036532012-01-26 15:43:51 -0800104 int ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700105 const struct modem_data *drv = dev_get_drvdata(pil->dev);
106
Stephen Boyd67036532012-01-26 15:43:51 -0800107 ret = make_modem_proxy_votes(pil->dev);
108 if (ret)
109 return ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700110
111 /* Put modem AHB0,1,2 clocks into reset */
112 writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET);
113 writel_relaxed(BIT(7), MAHB1_CLK_CTL);
114 writel_relaxed(BIT(7), MAHB2_CLK_CTL);
115
116 /* Vote for pll8 on behalf of the modem */
117 reg = readl_relaxed(PLL_ENA_MARM);
118 reg |= BIT(8);
119 writel_relaxed(reg, PLL_ENA_MARM);
120
121 /* Wait for PLL8 to enable */
122 while (!(readl_relaxed(PLL8_STATUS) & BIT(16)))
123 cpu_relax();
124
125 /* Set MAHB1 divider to Div-5 to run MAHB1,2 and sfab at 79.8 Mhz*/
126 writel_relaxed(0x4, MAHB1_NS);
127
128 /* Vote for modem AHB1 and 2 clocks to be on on behalf of the modem */
129 reg = readl_relaxed(MARM_CLK_BRANCH_ENA_VOTE);
130 reg |= BIT(0) | BIT(1);
131 writel_relaxed(reg, MARM_CLK_BRANCH_ENA_VOTE);
132
133 /* Source marm_clk off of PLL8 */
134 reg = readl_relaxed(MARM_CLK_SRC_CTL);
135 if ((reg & 0x1) == 0) {
136 writel_relaxed(0x3, MARM_CLK_SRC1_NS);
137 reg |= 0x1;
138 } else {
139 writel_relaxed(0x3, MARM_CLK_SRC0_NS);
140 reg &= ~0x1;
141 }
142 writel_relaxed(reg | 0x2, MARM_CLK_SRC_CTL);
143
144 /*
145 * Force core on and periph on signals to remain active during halt
146 * for marm_clk and mahb2_clk
147 */
148 writel_relaxed(0x6F, MARM_CLK_FS);
149 writel_relaxed(0x6F, MAHB2_CLK_FS);
150
151 /*
152 * Enable all of the marm_clk branches, cxo sourced marm branches,
153 * and sleep clock branches
154 */
155 writel_relaxed(0x10, MARM_CLK_CTL);
156 writel_relaxed(0x10, MAHB0_CLK_CTL);
157 writel_relaxed(0x10, SFAB_MSS_S_HCLK_CTL);
158 writel_relaxed(0x10, MSS_MODEM_CXO_CLK_CTL);
159 writel_relaxed(0x10, MSS_SLP_CLK_CTL);
160 writel_relaxed(0x10, MSS_MARM_SYS_REF_CLK_CTL);
161
162 /* Wait for above clocks to be turned on */
163 while (readl_relaxed(CLK_HALT_MSS_SMPSS_MISC_STATE) & (BIT(7) | BIT(8) |
164 BIT(9) | BIT(10) | BIT(4) | BIT(6)))
165 cpu_relax();
166
167 /* Take MAHB0,1,2 clocks out of reset */
168 writel_relaxed(0x0, MAHB2_CLK_CTL);
169 writel_relaxed(0x0, MAHB1_CLK_CTL);
170 writel_relaxed(0x0, MAHB0_SFAB_PORT_RESET);
171 mb();
172
173 /* Setup exception vector table base address */
174 writel_relaxed(drv->start_addr | 0x1, drv->base + MARM_BOOT_CONTROL);
175
176 /* Wait for vector table to be setup */
177 mb();
178
179 /* Bring modem out of reset */
180 writel_relaxed(0x0, MARM_RESET);
181
182 return 0;
183}
184
185static int modem_shutdown(struct pil_desc *pil)
186{
187 u32 reg;
188 struct modem_data *drv = dev_get_drvdata(pil->dev);
189
190 /* Put modem into reset */
191 writel_relaxed(0x1, MARM_RESET);
192 mb();
193
194 /* Put modem AHB0,1,2 clocks into reset */
195 writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET);
196 writel_relaxed(BIT(7), MAHB1_CLK_CTL);
197 writel_relaxed(BIT(7), MAHB2_CLK_CTL);
198 mb();
199
200 /*
201 * Disable all of the marm_clk branches, cxo sourced marm branches,
202 * and sleep clock branches
203 */
204 writel_relaxed(0x0, MARM_CLK_CTL);
205 writel_relaxed(0x0, MAHB0_CLK_CTL);
206 writel_relaxed(0x0, SFAB_MSS_S_HCLK_CTL);
207 writel_relaxed(0x0, MSS_MODEM_CXO_CLK_CTL);
208 writel_relaxed(0x0, MSS_SLP_CLK_CTL);
209 writel_relaxed(0x0, MSS_MARM_SYS_REF_CLK_CTL);
210
211 /* Disable marm_clk */
212 reg = readl_relaxed(MARM_CLK_SRC_CTL);
213 reg &= ~0x2;
214 writel_relaxed(reg, MARM_CLK_SRC_CTL);
215
216 /* Clear modem's votes for ahb clocks */
217 writel_relaxed(0x0, MARM_CLK_BRANCH_ENA_VOTE);
218
219 /* Clear modem's votes for PLLs */
220 writel_relaxed(0x0, PLL_ENA_MARM);
221
222 remove_modem_proxy_votes_now(drv);
223
224 return 0;
225}
226
227static struct pil_reset_ops pil_modem_ops = {
228 .init_image = modem_init_image,
229 .verify_blob = nop_verify_blob,
230 .auth_and_reset = modem_reset,
231 .shutdown = modem_shutdown,
232};
233
234static int modem_init_image_trusted(struct pil_desc *pil, const u8 *metadata,
235 size_t size)
236{
237 return pas_init_image(PAS_MODEM, metadata, size);
238}
239
240static int modem_reset_trusted(struct pil_desc *pil)
241{
242 int ret;
243 struct modem_data *drv = dev_get_drvdata(pil->dev);
244
Stephen Boyd67036532012-01-26 15:43:51 -0800245 ret = make_modem_proxy_votes(pil->dev);
246 if (ret)
247 return ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700248
249 ret = pas_auth_and_reset(PAS_MODEM);
250 if (ret)
251 remove_modem_proxy_votes_now(drv);
252
253 return ret;
254}
255
256static int modem_shutdown_trusted(struct pil_desc *pil)
257{
258 int ret;
259 struct modem_data *drv = dev_get_drvdata(pil->dev);
260
261 ret = pas_shutdown(PAS_MODEM);
262 if (ret)
263 return ret;
264
265 remove_modem_proxy_votes_now(drv);
266 return 0;
267}
268
269static struct pil_reset_ops pil_modem_ops_trusted = {
270 .init_image = modem_init_image_trusted,
271 .verify_blob = nop_verify_blob,
272 .auth_and_reset = modem_reset_trusted,
273 .shutdown = modem_shutdown_trusted,
274};
275
276static int __devinit pil_modem_driver_probe(struct platform_device *pdev)
277{
278 struct modem_data *drv;
279 struct resource *res;
280 struct pil_desc *desc;
281
282 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
283 if (!res)
284 return -EINVAL;
285
286 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
287 if (!drv)
288 return -ENOMEM;
289 platform_set_drvdata(pdev, drv);
290
291 drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
292 if (!drv->base)
293 return -ENOMEM;
294
Stephen Boyd67036532012-01-26 15:43:51 -0800295 drv->xo = clk_get(&pdev->dev, "xo");
296 if (IS_ERR(drv->xo))
297 return PTR_ERR(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700298
299 desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
300 if (!desc)
301 return -ENOMEM;
302
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700303 desc->name = "modem";
304 desc->depends_on = "q6";
305 desc->dev = &pdev->dev;
Stephen Boyd6d67d252011-09-27 11:50:05 -0700306 desc->owner = THIS_MODULE;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700307
308 if (pas_supported(PAS_MODEM) > 0) {
309 desc->ops = &pil_modem_ops_trusted;
310 dev_info(&pdev->dev, "using secure boot\n");
311 } else {
312 desc->ops = &pil_modem_ops;
313 dev_info(&pdev->dev, "using non-secure boot\n");
314 }
Stephen Boyd67036532012-01-26 15:43:51 -0800315 INIT_DELAYED_WORK(&drv->work, remove_modem_proxy_votes);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700316
Stephen Boyd6d67d252011-09-27 11:50:05 -0700317 drv->pil = msm_pil_register(desc);
318 if (IS_ERR(drv->pil)) {
Stephen Boyd67036532012-01-26 15:43:51 -0800319 flush_delayed_work_sync(&drv->work);
320 clk_put(drv->xo);
Stephen Boyd6d67d252011-09-27 11:50:05 -0700321 return PTR_ERR(drv->pil);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700322 }
Stephen Boyd6d67d252011-09-27 11:50:05 -0700323 return 0;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700324}
325
326static int __devexit pil_modem_driver_exit(struct platform_device *pdev)
327{
328 struct modem_data *drv = platform_get_drvdata(pdev);
Stephen Boyd6d67d252011-09-27 11:50:05 -0700329 msm_pil_unregister(drv->pil);
Stephen Boyd67036532012-01-26 15:43:51 -0800330 flush_delayed_work_sync(&drv->work);
331 clk_put(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700332 return 0;
333}
334
335static struct platform_driver pil_modem_driver = {
336 .probe = pil_modem_driver_probe,
337 .remove = __devexit_p(pil_modem_driver_exit),
338 .driver = {
339 .name = "pil_modem",
340 .owner = THIS_MODULE,
341 },
342};
343
344static int __init pil_modem_init(void)
345{
346 return platform_driver_register(&pil_modem_driver);
347}
348module_init(pil_modem_init);
349
350static void __exit pil_modem_exit(void)
351{
352 platform_driver_unregister(&pil_modem_driver);
353}
354module_exit(pil_modem_exit);
355
356MODULE_DESCRIPTION("Support for booting modem processors");
357MODULE_LICENSE("GPL v2");