blob: 80e0ac9ca6abddc02bcca8a404da37b77b1a33c8 [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
Stephen Boyd67036532012-01-26 15:43:51 -080061static int make_modem_proxy_votes(struct device *dev)
Stephen Boyd4eb885b2011-09-29 01:16:03 -070062{
63 int ret;
64 struct modem_data *drv = dev_get_drvdata(dev);
65
Stephen Boyd67036532012-01-26 15:43:51 -080066 ret = clk_prepare_enable(drv->xo);
67 if (ret) {
68 dev_err(dev, "Failed to enable XO\n");
69 return ret;
70 }
71 schedule_delayed_work(&drv->work, msecs_to_jiffies(PROXY_VOTE_TIMEOUT));
72 return 0;
73}
74
75static void remove_modem_proxy_votes(struct work_struct *work)
76{
77 struct modem_data *drv;
78 drv = container_of(work, struct modem_data, work.work);
79 clk_disable_unprepare(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -070080}
81
82static void remove_modem_proxy_votes_now(struct modem_data *drv)
83{
Stephen Boyd67036532012-01-26 15:43:51 -080084 flush_delayed_work(&drv->work);
Stephen Boyd4eb885b2011-09-29 01:16:03 -070085}
86
87static int modem_init_image(struct pil_desc *pil, const u8 *metadata,
88 size_t size)
89{
90 const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata;
91 struct modem_data *drv = dev_get_drvdata(pil->dev);
92 drv->start_addr = ehdr->e_entry;
93 return 0;
94}
95
96static int modem_reset(struct pil_desc *pil)
97{
98 u32 reg;
Stephen Boyd67036532012-01-26 15:43:51 -080099 int ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700100 const struct modem_data *drv = dev_get_drvdata(pil->dev);
101
Stephen Boyd67036532012-01-26 15:43:51 -0800102 ret = make_modem_proxy_votes(pil->dev);
103 if (ret)
104 return ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700105
106 /* Put modem AHB0,1,2 clocks into reset */
107 writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET);
108 writel_relaxed(BIT(7), MAHB1_CLK_CTL);
109 writel_relaxed(BIT(7), MAHB2_CLK_CTL);
110
111 /* Vote for pll8 on behalf of the modem */
112 reg = readl_relaxed(PLL_ENA_MARM);
113 reg |= BIT(8);
114 writel_relaxed(reg, PLL_ENA_MARM);
115
116 /* Wait for PLL8 to enable */
117 while (!(readl_relaxed(PLL8_STATUS) & BIT(16)))
118 cpu_relax();
119
120 /* Set MAHB1 divider to Div-5 to run MAHB1,2 and sfab at 79.8 Mhz*/
121 writel_relaxed(0x4, MAHB1_NS);
122
123 /* Vote for modem AHB1 and 2 clocks to be on on behalf of the modem */
124 reg = readl_relaxed(MARM_CLK_BRANCH_ENA_VOTE);
125 reg |= BIT(0) | BIT(1);
126 writel_relaxed(reg, MARM_CLK_BRANCH_ENA_VOTE);
127
128 /* Source marm_clk off of PLL8 */
129 reg = readl_relaxed(MARM_CLK_SRC_CTL);
130 if ((reg & 0x1) == 0) {
131 writel_relaxed(0x3, MARM_CLK_SRC1_NS);
132 reg |= 0x1;
133 } else {
134 writel_relaxed(0x3, MARM_CLK_SRC0_NS);
135 reg &= ~0x1;
136 }
137 writel_relaxed(reg | 0x2, MARM_CLK_SRC_CTL);
138
139 /*
140 * Force core on and periph on signals to remain active during halt
141 * for marm_clk and mahb2_clk
142 */
143 writel_relaxed(0x6F, MARM_CLK_FS);
144 writel_relaxed(0x6F, MAHB2_CLK_FS);
145
146 /*
147 * Enable all of the marm_clk branches, cxo sourced marm branches,
148 * and sleep clock branches
149 */
150 writel_relaxed(0x10, MARM_CLK_CTL);
151 writel_relaxed(0x10, MAHB0_CLK_CTL);
152 writel_relaxed(0x10, SFAB_MSS_S_HCLK_CTL);
153 writel_relaxed(0x10, MSS_MODEM_CXO_CLK_CTL);
154 writel_relaxed(0x10, MSS_SLP_CLK_CTL);
155 writel_relaxed(0x10, MSS_MARM_SYS_REF_CLK_CTL);
156
157 /* Wait for above clocks to be turned on */
158 while (readl_relaxed(CLK_HALT_MSS_SMPSS_MISC_STATE) & (BIT(7) | BIT(8) |
159 BIT(9) | BIT(10) | BIT(4) | BIT(6)))
160 cpu_relax();
161
162 /* Take MAHB0,1,2 clocks out of reset */
163 writel_relaxed(0x0, MAHB2_CLK_CTL);
164 writel_relaxed(0x0, MAHB1_CLK_CTL);
165 writel_relaxed(0x0, MAHB0_SFAB_PORT_RESET);
166 mb();
167
168 /* Setup exception vector table base address */
169 writel_relaxed(drv->start_addr | 0x1, drv->base + MARM_BOOT_CONTROL);
170
171 /* Wait for vector table to be setup */
172 mb();
173
174 /* Bring modem out of reset */
175 writel_relaxed(0x0, MARM_RESET);
176
177 return 0;
178}
179
180static int modem_shutdown(struct pil_desc *pil)
181{
182 u32 reg;
183 struct modem_data *drv = dev_get_drvdata(pil->dev);
184
185 /* Put modem into reset */
186 writel_relaxed(0x1, MARM_RESET);
187 mb();
188
189 /* Put modem AHB0,1,2 clocks into reset */
190 writel_relaxed(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET);
191 writel_relaxed(BIT(7), MAHB1_CLK_CTL);
192 writel_relaxed(BIT(7), MAHB2_CLK_CTL);
193 mb();
194
195 /*
196 * Disable all of the marm_clk branches, cxo sourced marm branches,
197 * and sleep clock branches
198 */
199 writel_relaxed(0x0, MARM_CLK_CTL);
200 writel_relaxed(0x0, MAHB0_CLK_CTL);
201 writel_relaxed(0x0, SFAB_MSS_S_HCLK_CTL);
202 writel_relaxed(0x0, MSS_MODEM_CXO_CLK_CTL);
203 writel_relaxed(0x0, MSS_SLP_CLK_CTL);
204 writel_relaxed(0x0, MSS_MARM_SYS_REF_CLK_CTL);
205
206 /* Disable marm_clk */
207 reg = readl_relaxed(MARM_CLK_SRC_CTL);
208 reg &= ~0x2;
209 writel_relaxed(reg, MARM_CLK_SRC_CTL);
210
211 /* Clear modem's votes for ahb clocks */
212 writel_relaxed(0x0, MARM_CLK_BRANCH_ENA_VOTE);
213
214 /* Clear modem's votes for PLLs */
215 writel_relaxed(0x0, PLL_ENA_MARM);
216
217 remove_modem_proxy_votes_now(drv);
218
219 return 0;
220}
221
222static struct pil_reset_ops pil_modem_ops = {
223 .init_image = modem_init_image,
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700224 .auth_and_reset = modem_reset,
225 .shutdown = modem_shutdown,
226};
227
228static int modem_init_image_trusted(struct pil_desc *pil, const u8 *metadata,
229 size_t size)
230{
231 return pas_init_image(PAS_MODEM, metadata, size);
232}
233
234static int modem_reset_trusted(struct pil_desc *pil)
235{
236 int ret;
237 struct modem_data *drv = dev_get_drvdata(pil->dev);
238
Stephen Boyd67036532012-01-26 15:43:51 -0800239 ret = make_modem_proxy_votes(pil->dev);
240 if (ret)
241 return ret;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700242
243 ret = pas_auth_and_reset(PAS_MODEM);
244 if (ret)
245 remove_modem_proxy_votes_now(drv);
246
247 return ret;
248}
249
250static int modem_shutdown_trusted(struct pil_desc *pil)
251{
252 int ret;
253 struct modem_data *drv = dev_get_drvdata(pil->dev);
254
255 ret = pas_shutdown(PAS_MODEM);
256 if (ret)
257 return ret;
258
259 remove_modem_proxy_votes_now(drv);
260 return 0;
261}
262
263static struct pil_reset_ops pil_modem_ops_trusted = {
264 .init_image = modem_init_image_trusted,
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700265 .auth_and_reset = modem_reset_trusted,
266 .shutdown = modem_shutdown_trusted,
267};
268
269static int __devinit pil_modem_driver_probe(struct platform_device *pdev)
270{
271 struct modem_data *drv;
272 struct resource *res;
273 struct pil_desc *desc;
274
275 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
276 if (!res)
277 return -EINVAL;
278
279 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
280 if (!drv)
281 return -ENOMEM;
282 platform_set_drvdata(pdev, drv);
283
284 drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
285 if (!drv->base)
286 return -ENOMEM;
287
Stephen Boyd67036532012-01-26 15:43:51 -0800288 drv->xo = clk_get(&pdev->dev, "xo");
289 if (IS_ERR(drv->xo))
290 return PTR_ERR(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700291
292 desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
293 if (!desc)
294 return -ENOMEM;
295
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700296 desc->name = "modem";
297 desc->depends_on = "q6";
298 desc->dev = &pdev->dev;
Stephen Boyd6d67d252011-09-27 11:50:05 -0700299 desc->owner = THIS_MODULE;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700300
301 if (pas_supported(PAS_MODEM) > 0) {
302 desc->ops = &pil_modem_ops_trusted;
303 dev_info(&pdev->dev, "using secure boot\n");
304 } else {
305 desc->ops = &pil_modem_ops;
306 dev_info(&pdev->dev, "using non-secure boot\n");
307 }
Stephen Boyd67036532012-01-26 15:43:51 -0800308 INIT_DELAYED_WORK(&drv->work, remove_modem_proxy_votes);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700309
Stephen Boyd6d67d252011-09-27 11:50:05 -0700310 drv->pil = msm_pil_register(desc);
311 if (IS_ERR(drv->pil)) {
Stephen Boyd67036532012-01-26 15:43:51 -0800312 flush_delayed_work_sync(&drv->work);
313 clk_put(drv->xo);
Stephen Boyd6d67d252011-09-27 11:50:05 -0700314 return PTR_ERR(drv->pil);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700315 }
Stephen Boyd6d67d252011-09-27 11:50:05 -0700316 return 0;
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700317}
318
319static int __devexit pil_modem_driver_exit(struct platform_device *pdev)
320{
321 struct modem_data *drv = platform_get_drvdata(pdev);
Stephen Boyd6d67d252011-09-27 11:50:05 -0700322 msm_pil_unregister(drv->pil);
Stephen Boyd67036532012-01-26 15:43:51 -0800323 flush_delayed_work_sync(&drv->work);
324 clk_put(drv->xo);
Stephen Boyd4eb885b2011-09-29 01:16:03 -0700325 return 0;
326}
327
328static struct platform_driver pil_modem_driver = {
329 .probe = pil_modem_driver_probe,
330 .remove = __devexit_p(pil_modem_driver_exit),
331 .driver = {
332 .name = "pil_modem",
333 .owner = THIS_MODULE,
334 },
335};
336
337static int __init pil_modem_init(void)
338{
339 return platform_driver_register(&pil_modem_driver);
340}
341module_init(pil_modem_init);
342
343static void __exit pil_modem_exit(void)
344{
345 platform_driver_unregister(&pil_modem_driver);
346}
347module_exit(pil_modem_exit);
348
349MODULE_DESCRIPTION("Support for booting modem processors");
350MODULE_LICENSE("GPL v2");