blob: ee01f0479a1337ca66fea9898e806b15cd491346 [file] [log] [blame]
Stephen Boydbdb53f32012-06-05 18:39:47 -07001/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
2 *
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/regulator/consumer.h>
17#include <linux/err.h>
18#include <linux/io.h>
19#include <linux/delay.h>
20#include <linux/clk.h>
Stephen Boyd2efa9962012-06-12 14:20:12 -070021#include <linux/interrupt.h>
Stephen Boydbdb53f32012-06-05 18:39:47 -070022
Stephen Boyd2efa9962012-06-12 14:20:12 -070023#include <mach/subsystem_restart.h>
24#include <mach/msm_smsm.h>
Stephen Boydbdb53f32012-06-05 18:39:47 -070025
Stephen Boyd2efa9962012-06-12 14:20:12 -070026#include "smd_private.h"
27#include "ramdump.h"
Stephen Boydbdb53f32012-06-05 18:39:47 -070028#include "peripheral-loader.h"
29#include "pil-q6v4.h"
30#include "scm-pas.h"
31
Stephen Boyde24edf52012-07-12 17:46:19 -070032#define MSS_S_HCLK_CTL 0x2C70
33#define MSS_SLP_CLK_CTL 0x2C60
34#define SFAB_MSS_M_ACLK_CTL 0x2340
35#define SFAB_MSS_S_HCLK_CTL 0x2C00
36#define MSS_RESET 0x2C64
Stephen Boydbdb53f32012-06-05 18:39:47 -070037
38struct q6v4_modem {
39 struct q6v4_data q6_fw;
40 struct q6v4_data q6_sw;
41 void __iomem *modem_base;
Stephen Boyde24edf52012-07-12 17:46:19 -070042 void __iomem *cbase;
Stephen Boyd2efa9962012-06-12 14:20:12 -070043 void *fw_ramdump_dev;
44 void *sw_ramdump_dev;
45 void *smem_ramdump_dev;
46 struct subsys_device *subsys;
47 struct subsys_desc subsys_desc;
48 int crash_shutdown;
49 int loadable;
Stephen Boyd3e4e9752012-06-27 12:46:32 -070050 void *pil;
Stephen Boydbdb53f32012-06-05 18:39:47 -070051};
52
53static DEFINE_MUTEX(pil_q6v4_modem_lock);
54static unsigned pil_q6v4_modem_count;
55
56/* Bring modem subsystem out of reset */
Stephen Boyde24edf52012-07-12 17:46:19 -070057static void pil_q6v4_init_modem(void __iomem *base, void __iomem *cbase,
58 void __iomem *jtag_clk)
Stephen Boydbdb53f32012-06-05 18:39:47 -070059{
60 mutex_lock(&pil_q6v4_modem_lock);
61 if (!pil_q6v4_modem_count) {
62 /* Enable MSS clocks */
Stephen Boyde24edf52012-07-12 17:46:19 -070063 writel_relaxed(0x10, cbase + SFAB_MSS_M_ACLK_CTL);
64 writel_relaxed(0x10, cbase + SFAB_MSS_S_HCLK_CTL);
65 writel_relaxed(0x10, cbase + MSS_S_HCLK_CTL);
66 writel_relaxed(0x10, cbase + MSS_SLP_CLK_CTL);
Stephen Boydbdb53f32012-06-05 18:39:47 -070067 /* Wait for clocks to enable */
68 mb();
69 udelay(10);
70
71 /* De-assert MSS reset */
Stephen Boyde24edf52012-07-12 17:46:19 -070072 writel_relaxed(0x0, cbase + MSS_RESET);
Stephen Boydbdb53f32012-06-05 18:39:47 -070073 mb();
74 udelay(10);
75 /* Enable MSS */
76 writel_relaxed(0x7, base);
77 }
78
79 /* Enable JTAG clocks */
80 /* TODO: Remove if/when Q6 software enables them? */
81 writel_relaxed(0x10, jtag_clk);
82
83 pil_q6v4_modem_count++;
84 mutex_unlock(&pil_q6v4_modem_lock);
85}
86
87/* Put modem subsystem back into reset */
Stephen Boyde24edf52012-07-12 17:46:19 -070088static void pil_q6v4_shutdown_modem(struct q6v4_modem *mdm)
Stephen Boydbdb53f32012-06-05 18:39:47 -070089{
90 mutex_lock(&pil_q6v4_modem_lock);
91 if (pil_q6v4_modem_count)
92 pil_q6v4_modem_count--;
93 if (pil_q6v4_modem_count == 0)
Stephen Boyde24edf52012-07-12 17:46:19 -070094 writel_relaxed(0x1, mdm->cbase + MSS_RESET);
Stephen Boydbdb53f32012-06-05 18:39:47 -070095 mutex_unlock(&pil_q6v4_modem_lock);
96}
97
98static int pil_q6v4_modem_boot(struct pil_desc *pil)
99{
100 struct q6v4_data *drv = pil_to_q6v4_data(pil);
101 struct q6v4_modem *mdm = dev_get_drvdata(pil->dev);
102 int err;
103
104 err = pil_q6v4_power_up(drv);
105 if (err)
106 return err;
107
Stephen Boyde24edf52012-07-12 17:46:19 -0700108 pil_q6v4_init_modem(mdm->modem_base, mdm->cbase, drv->jtag_clk_reg);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700109 return pil_q6v4_boot(pil);
110}
111
112static int pil_q6v4_modem_shutdown(struct pil_desc *pil)
113{
114 struct q6v4_data *drv = pil_to_q6v4_data(pil);
Stephen Boyde24edf52012-07-12 17:46:19 -0700115 struct q6v4_modem *mdm = dev_get_drvdata(pil->dev);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700116 int ret;
117
118 ret = pil_q6v4_shutdown(pil);
119 if (ret)
120 return ret;
Stephen Boyde24edf52012-07-12 17:46:19 -0700121 pil_q6v4_shutdown_modem(mdm);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700122 pil_q6v4_power_down(drv);
123 return 0;
124}
125
126static struct pil_reset_ops pil_q6v4_modem_ops = {
Stephen Boydbdb53f32012-06-05 18:39:47 -0700127 .auth_and_reset = pil_q6v4_modem_boot,
128 .shutdown = pil_q6v4_modem_shutdown,
129 .proxy_vote = pil_q6v4_make_proxy_votes,
130 .proxy_unvote = pil_q6v4_remove_proxy_votes,
131};
132
133static struct pil_reset_ops pil_q6v4_modem_ops_trusted = {
134 .init_image = pil_q6v4_init_image_trusted,
135 .auth_and_reset = pil_q6v4_boot_trusted,
136 .shutdown = pil_q6v4_shutdown_trusted,
137 .proxy_vote = pil_q6v4_make_proxy_votes,
138 .proxy_unvote = pil_q6v4_remove_proxy_votes,
139};
140
Stephen Boyd2efa9962012-06-12 14:20:12 -0700141static void log_modem_sfr(void)
142{
143 u32 size;
144 char *smem_reason, reason[81];
145
146 smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size);
147 if (!smem_reason || !size) {
148 pr_err("modem subsystem failure reason: (unknown, smem_get_entry failed).\n");
149 return;
150 }
151 if (!smem_reason[0]) {
152 pr_err("modem subsystem failure reason: (unknown, init string found).\n");
153 return;
154 }
155
156 size = min(size, sizeof(reason)-1);
157 memcpy(reason, smem_reason, size);
158 reason[size] = '\0';
159 pr_err("modem subsystem failure reason: %s.\n", reason);
160
161 smem_reason[0] = '\0';
162 wmb();
163}
164
165static void restart_modem(struct q6v4_modem *drv)
166{
167 log_modem_sfr();
168 subsystem_restart_dev(drv->subsys);
169}
170
171#define desc_to_modem(d) container_of(d, struct q6v4_modem, subsys_desc)
172
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700173static int modem_start(const struct subsys_desc *desc)
174{
175 struct q6v4_modem *drv = desc_to_modem(desc);
Stephen Boyde83a0a22012-06-29 13:51:27 -0700176 int ret = 0;
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700177
178 if (drv->loadable) {
Stephen Boyde83a0a22012-06-29 13:51:27 -0700179 ret = pil_boot(&drv->q6_fw.desc);
180 if (ret)
181 return ret;
182 ret = pil_boot(&drv->q6_sw.desc);
183 if (ret)
184 pil_shutdown(&drv->q6_fw.desc);
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700185 }
Stephen Boyde83a0a22012-06-29 13:51:27 -0700186 return ret;
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700187}
188
189static void modem_stop(const struct subsys_desc *desc)
190{
191 struct q6v4_modem *drv = desc_to_modem(desc);
Stephen Boyde83a0a22012-06-29 13:51:27 -0700192 if (drv->loadable) {
193 pil_shutdown(&drv->q6_sw.desc);
194 pil_shutdown(&drv->q6_fw.desc);
195 }
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700196}
197
Stephen Boyd2efa9962012-06-12 14:20:12 -0700198static int modem_shutdown(const struct subsys_desc *subsys)
199{
200 struct q6v4_modem *drv = desc_to_modem(subsys);
201
202 /* The watchdogs keep running even after the modem is shutdown */
203 writel_relaxed(0x0, drv->q6_fw.wdog_base + 0x24);
204 writel_relaxed(0x0, drv->q6_sw.wdog_base + 0x24);
205 mb();
206
207 if (drv->loadable) {
Stephen Boyde83a0a22012-06-29 13:51:27 -0700208 pil_shutdown(&drv->q6_sw.desc);
209 pil_shutdown(&drv->q6_fw.desc);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700210 }
211
212 disable_irq_nosync(drv->q6_fw.wdog_irq);
213 disable_irq_nosync(drv->q6_sw.wdog_irq);
214
215 return 0;
216}
217
218static int modem_powerup(const struct subsys_desc *subsys)
219{
220 struct q6v4_modem *drv = desc_to_modem(subsys);
Stephen Boyde83a0a22012-06-29 13:51:27 -0700221 int ret;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700222
223 if (drv->loadable) {
Stephen Boyde83a0a22012-06-29 13:51:27 -0700224 ret = pil_boot(&drv->q6_fw.desc);
225 if (ret)
226 return ret;
227 ret = pil_boot(&drv->q6_sw.desc);
228 if (ret) {
229 pil_shutdown(&drv->q6_fw.desc);
230 return ret;
231 }
Stephen Boyd2efa9962012-06-12 14:20:12 -0700232 }
233 enable_irq(drv->q6_fw.wdog_irq);
234 enable_irq(drv->q6_sw.wdog_irq);
235 return 0;
236}
237
238void modem_crash_shutdown(const struct subsys_desc *subsys)
239{
240 struct q6v4_modem *drv = desc_to_modem(subsys);
241
242 drv->crash_shutdown = 1;
243 smsm_reset_modem(SMSM_RESET);
244}
245
246static struct ramdump_segment sw_segments[] = {
247 {0x89000000, 0x8D400000 - 0x89000000},
248};
249
250static struct ramdump_segment fw_segments[] = {
251 {0x8D400000, 0x8DA00000 - 0x8D400000},
252};
253
254static struct ramdump_segment smem_segments[] = {
255 {0x80000000, 0x00200000},
256};
257
258static int modem_ramdump(int enable, const struct subsys_desc *subsys)
259{
260 struct q6v4_modem *drv = desc_to_modem(subsys);
261 int ret;
262
263 if (!enable)
264 return 0;
265
266 ret = do_ramdump(drv->sw_ramdump_dev, sw_segments,
267 ARRAY_SIZE(sw_segments));
268 if (ret < 0)
269 return ret;
270
271 ret = do_ramdump(drv->fw_ramdump_dev, fw_segments,
272 ARRAY_SIZE(fw_segments));
273 if (ret < 0)
274 return ret;
275
276 ret = do_ramdump(drv->smem_ramdump_dev, smem_segments,
277 ARRAY_SIZE(smem_segments));
278 if (ret < 0)
279 return ret;
280
281 return 0;
282}
283
284static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state)
285{
286 struct q6v4_modem *drv = data;
287
288 /* Ignore if we're the one that set SMSM_RESET */
289 if (drv->crash_shutdown)
290 return;
291
292 if (new_state & SMSM_RESET) {
293 pr_err("Probable fatal error on the modem.\n");
294 restart_modem(drv);
295 }
296}
297
298static irqreturn_t modem_wdog_bite_irq(int irq, void *dev_id)
299{
300 struct q6v4_modem *drv = dev_id;
301 restart_modem(drv);
302 return IRQ_HANDLED;
303}
304
Stephen Boydbdb53f32012-06-05 18:39:47 -0700305static int __devinit
306pil_q6v4_proc_init(struct q6v4_data *drv, struct platform_device *pdev, int i)
307{
308 static const char *name[2] = { "fw", "sw" };
309 const struct pil_q6v4_pdata *pdata_p = pdev->dev.platform_data;
310 const struct pil_q6v4_pdata *pdata = pdata_p + i;
311 char reg_name[12];
312 struct pil_desc *desc;
313 struct resource *res;
314
Stephen Boyde24edf52012-07-12 17:46:19 -0700315 res = platform_get_resource(pdev, IORESOURCE_MEM, 2 + (i * 2));
Stephen Boydf8f89282012-07-16 18:05:48 -0700316 drv->base = devm_request_and_ioremap(&pdev->dev, res);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700317 if (!drv->base)
318 return -ENOMEM;
319
Stephen Boyde24edf52012-07-12 17:46:19 -0700320 res = platform_get_resource(pdev, IORESOURCE_MEM, 3 + (i * 2));
Stephen Boydf8f89282012-07-16 18:05:48 -0700321 drv->wdog_base = devm_request_and_ioremap(&pdev->dev, res);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700322 if (!drv->wdog_base)
323 return -ENOMEM;
324
Stephen Boydbdb53f32012-06-05 18:39:47 -0700325 snprintf(reg_name, sizeof(reg_name), "%s_core_vdd", name[i]);
326 drv->vreg = devm_regulator_get(&pdev->dev, reg_name);
327 if (IS_ERR(drv->vreg))
328 return PTR_ERR(drv->vreg);
329
330 drv->xo = devm_clk_get(&pdev->dev, "xo");
331 if (IS_ERR(drv->xo))
332 return PTR_ERR(drv->xo);
333
334 desc = &drv->desc;
335 desc->name = pdata->name;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700336 desc->dev = &pdev->dev;
337 desc->owner = THIS_MODULE;
338 desc->proxy_timeout = 10000;
339 pil_q6v4_init(drv, pdata);
340
341 if (pas_supported(pdata->pas_id) > 0) {
342 desc->ops = &pil_q6v4_modem_ops_trusted;
343 dev_info(&pdev->dev, "using secure boot for %s\n", name[i]);
344 } else {
345 desc->ops = &pil_q6v4_modem_ops;
346 dev_info(&pdev->dev, "using non-secure boot for %s\n", name[i]);
347 }
348 return 0;
349}
350
351static int __devinit pil_q6v4_modem_driver_probe(struct platform_device *pdev)
352{
353 struct q6v4_data *drv_fw, *drv_sw;
354 struct q6v4_modem *drv;
355 struct resource *res;
356 struct regulator *pll_supply;
357 int ret;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700358 const struct pil_q6v4_pdata *pdata = pdev->dev.platform_data;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700359
360 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
361 if (!drv)
362 return -ENOMEM;
363 platform_set_drvdata(pdev, drv);
364
365 drv_fw = &drv->q6_fw;
366 drv_sw = &drv->q6_sw;
367
Stephen Boyd2efa9962012-06-12 14:20:12 -0700368 drv_fw->wdog_irq = platform_get_irq(pdev, 0);
369 if (drv_fw->wdog_irq < 0)
370 return drv_fw->wdog_irq;
371
372 drv_sw->wdog_irq = platform_get_irq(pdev, 1);
373 if (drv_sw->wdog_irq < 0)
374 return drv_sw->wdog_irq;
375
376 drv->loadable = !!pdata; /* No pdata = don't use PIL */
377 if (drv->loadable) {
378 ret = pil_q6v4_proc_init(drv_fw, pdev, 0);
379 if (ret)
380 return ret;
381
382 ret = pil_q6v4_proc_init(drv_sw, pdev, 1);
383 if (ret)
384 return ret;
385
386 pll_supply = devm_regulator_get(&pdev->dev, "pll_vdd");
387 drv_fw->pll_supply = drv_sw->pll_supply = pll_supply;
388 if (IS_ERR(pll_supply))
389 return PTR_ERR(pll_supply);
390
391 ret = regulator_set_voltage(pll_supply, 1800000, 1800000);
392 if (ret) {
393 dev_err(&pdev->dev, "failed to set pll voltage\n");
394 return ret;
395 }
396
397 ret = regulator_set_optimum_mode(pll_supply, 100000);
398 if (ret < 0) {
399 dev_err(&pdev->dev, "failed to set pll optimum mode\n");
400 return ret;
401 }
402
403 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Stephen Boydf8f89282012-07-16 18:05:48 -0700404 drv->modem_base = devm_request_and_ioremap(&pdev->dev, res);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700405 if (!drv->modem_base)
406 return -ENOMEM;
407
Stephen Boyde24edf52012-07-12 17:46:19 -0700408 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
409 if (!res)
410 return -EINVAL;
411 drv->cbase = devm_ioremap(&pdev->dev, res->start,
412 resource_size(res));
413 if (!drv->cbase)
414 return -ENOMEM;
415
Stephen Boyde83a0a22012-06-29 13:51:27 -0700416 ret = pil_desc_init(&drv_fw->desc);
417 if (ret)
418 return ret;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700419
Stephen Boyde83a0a22012-06-29 13:51:27 -0700420 ret = pil_desc_init(&drv_sw->desc);
421 if (ret)
Stephen Boyd2efa9962012-06-12 14:20:12 -0700422 goto err_pil_sw;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700423 }
424
425 drv->subsys_desc.name = "modem";
Stephen Boyd77db8bb2012-06-27 15:15:16 -0700426 drv->subsys_desc.depends_on = "adsp";
Stephen Boyd3e4e9752012-06-27 12:46:32 -0700427 drv->subsys_desc.dev = &pdev->dev;
428 drv->subsys_desc.owner = THIS_MODULE;
429 drv->subsys_desc.start = modem_start;
430 drv->subsys_desc.stop = modem_stop;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700431 drv->subsys_desc.shutdown = modem_shutdown;
432 drv->subsys_desc.powerup = modem_powerup;
433 drv->subsys_desc.ramdump = modem_ramdump;
434 drv->subsys_desc.crash_shutdown = modem_crash_shutdown;
435
Stephen Boydc1a72612012-07-05 14:07:35 -0700436 drv->fw_ramdump_dev = create_ramdump_device("modem_fw", &pdev->dev);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700437 if (!drv->fw_ramdump_dev) {
438 ret = -ENOMEM;
439 goto err_fw_ramdump;
440 }
441
Stephen Boydc1a72612012-07-05 14:07:35 -0700442 drv->sw_ramdump_dev = create_ramdump_device("modem_sw", &pdev->dev);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700443 if (!drv->sw_ramdump_dev) {
444 ret = -ENOMEM;
445 goto err_sw_ramdump;
446 }
447
Stephen Boydc1a72612012-07-05 14:07:35 -0700448 drv->smem_ramdump_dev = create_ramdump_device("smem-modem", &pdev->dev);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700449 if (!drv->smem_ramdump_dev) {
450 ret = -ENOMEM;
451 goto err_smem_ramdump;
452 }
453
454 drv->subsys = subsys_register(&drv->subsys_desc);
455 if (IS_ERR(drv->subsys)) {
456 ret = PTR_ERR(drv->subsys);
457 goto err_subsys;
458 }
Stephen Boyd43b380a2012-09-21 17:34:24 -0700459 if (!drv->loadable)
460 subsys_default_online(drv->subsys);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700461
462 ret = devm_request_irq(&pdev->dev, drv_fw->wdog_irq,
463 modem_wdog_bite_irq, IRQF_TRIGGER_RISING,
464 dev_name(&pdev->dev), drv);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700465 if (ret)
Stephen Boyd2efa9962012-06-12 14:20:12 -0700466 goto err_irq;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700467
Stephen Boyd2efa9962012-06-12 14:20:12 -0700468 ret = devm_request_irq(&pdev->dev, drv_sw->wdog_irq,
469 modem_wdog_bite_irq, IRQF_TRIGGER_RISING,
470 dev_name(&pdev->dev), drv);
Stephen Boydbdb53f32012-06-05 18:39:47 -0700471 if (ret)
Stephen Boyd2efa9962012-06-12 14:20:12 -0700472 goto err_irq;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700473
Stephen Boyd2efa9962012-06-12 14:20:12 -0700474 ret = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_RESET,
475 smsm_state_cb, drv);
476 if (ret)
477 goto err_irq;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700478 return 0;
Stephen Boyd2efa9962012-06-12 14:20:12 -0700479
480err_irq:
481 subsys_unregister(drv->subsys);
482err_subsys:
483 destroy_ramdump_device(drv->smem_ramdump_dev);
484err_smem_ramdump:
485 destroy_ramdump_device(drv->sw_ramdump_dev);
486err_sw_ramdump:
487 destroy_ramdump_device(drv->fw_ramdump_dev);
488err_fw_ramdump:
489 if (drv->loadable)
Stephen Boyde83a0a22012-06-29 13:51:27 -0700490 pil_desc_release(&drv_sw->desc);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700491err_pil_sw:
Stephen Boyde83a0a22012-06-29 13:51:27 -0700492 pil_desc_release(&drv_fw->desc);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700493 return ret;
Stephen Boydbdb53f32012-06-05 18:39:47 -0700494}
495
496static int __devexit pil_q6v4_modem_driver_exit(struct platform_device *pdev)
497{
498 struct q6v4_modem *drv = platform_get_drvdata(pdev);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700499
500 smsm_state_cb_deregister(SMSM_MODEM_STATE, SMSM_RESET,
501 smsm_state_cb, drv);
502 subsys_unregister(drv->subsys);
503 destroy_ramdump_device(drv->smem_ramdump_dev);
504 destroy_ramdump_device(drv->sw_ramdump_dev);
505 destroy_ramdump_device(drv->fw_ramdump_dev);
506 if (drv->loadable) {
Stephen Boyde83a0a22012-06-29 13:51:27 -0700507 pil_desc_release(&drv->q6_sw.desc);
508 pil_desc_release(&drv->q6_fw.desc);
Stephen Boyd2efa9962012-06-12 14:20:12 -0700509 }
Stephen Boydbdb53f32012-06-05 18:39:47 -0700510 return 0;
511}
512
513static struct platform_driver pil_q6v4_modem_driver = {
514 .probe = pil_q6v4_modem_driver_probe,
515 .remove = __devexit_p(pil_q6v4_modem_driver_exit),
516 .driver = {
517 .name = "pil-q6v4-modem",
518 .owner = THIS_MODULE,
519 },
520};
521
522static int __init pil_q6v4_modem_init(void)
523{
524 return platform_driver_register(&pil_q6v4_modem_driver);
525}
526module_init(pil_q6v4_modem_init);
527
528static void __exit pil_q6v4_modem_exit(void)
529{
530 platform_driver_unregister(&pil_q6v4_modem_driver);
531}
532module_exit(pil_q6v4_modem_exit);
533
534MODULE_DESCRIPTION("Support for booting QDSP6v4 (Hexagon) processors");
535MODULE_LICENSE("GPL v2");