blob: 511377dab45ef54d658a42b5becb74151e68ac92 [file] [log] [blame]
Stephen Boydcc0f5342011-12-29 17:28:57 -08001/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
Stephen Boydeb819882011-08-29 14:46:30 -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/regulator/consumer.h>
19#include <linux/elf.h>
20#include <linux/delay.h>
21#include <linux/err.h>
Stephen Boydcc0f5342011-12-29 17:28:57 -080022#include <linux/workqueue.h>
Stephen Boyded630b02012-01-26 15:26:47 -080023#include <linux/clk.h>
Stephen Boydeb819882011-08-29 14:46:30 -070024
Matt Wagantall6e4aafb2011-09-09 17:53:54 -070025#include <mach/msm_bus.h>
Stephen Boydeb819882011-08-29 14:46:30 -070026#include <mach/msm_iomap.h>
27
28#include "peripheral-loader.h"
29#include "pil-q6v4.h"
30#include "scm-pas.h"
31
Matt Wagantall44175262011-12-16 15:36:14 -080032#define PROXY_VOTE_TIMEOUT 40000
Matt Wagantall39088932011-08-02 20:24:56 -070033
Stephen Boydeb819882011-08-29 14:46:30 -070034#define QDSP6SS_RST_EVB 0x0
35#define QDSP6SS_RESET 0x04
36#define QDSP6SS_CGC_OVERRIDE 0x18
37#define QDSP6SS_STRAP_TCM 0x1C
38#define QDSP6SS_STRAP_AHB 0x20
39#define QDSP6SS_GFMUX_CTL 0x30
40#define QDSP6SS_PWR_CTL 0x38
41
42#define MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C70)
43#define MSS_SLP_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C60)
44#define SFAB_MSS_M_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2340)
45#define SFAB_MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C00)
46#define MSS_RESET (MSM_CLK_CTL_BASE + 0x2C64)
47
48#define Q6SS_SS_ARES BIT(0)
49#define Q6SS_CORE_ARES BIT(1)
50#define Q6SS_ISDB_ARES BIT(2)
51#define Q6SS_ETM_ARES BIT(3)
52#define Q6SS_STOP_CORE_ARES BIT(4)
53#define Q6SS_PRIV_ARES BIT(5)
54
55#define Q6SS_L2DATA_SLP_NRET_N BIT(0)
56#define Q6SS_SLP_RET_N BIT(1)
57#define Q6SS_L1TCM_SLP_NRET_N BIT(2)
58#define Q6SS_L2TAG_SLP_NRET_N BIT(3)
59#define Q6SS_ETB_SLEEP_NRET_N BIT(4)
60#define Q6SS_ARR_STBY_N BIT(5)
61#define Q6SS_CLAMP_IO BIT(6)
62
63#define Q6SS_CLK_ENA BIT(1)
64#define Q6SS_SRC_SWITCH_CLK_OVR BIT(8)
65#define Q6SS_AXIS_ACLK_EN BIT(9)
66
67struct q6v4_data {
68 void __iomem *base;
69 void __iomem *modem_base;
70 unsigned long start_addr;
71 struct regulator *vreg;
Stephen Boydcc0f5342011-12-29 17:28:57 -080072 struct regulator *pll_supply;
Stephen Boydeb819882011-08-29 14:46:30 -070073 bool vreg_enabled;
Stephen Boyded630b02012-01-26 15:26:47 -080074 struct clk *xo;
Stephen Boydcc0f5342011-12-29 17:28:57 -080075 struct delayed_work work;
Stephen Boydeb819882011-08-29 14:46:30 -070076};
77
78static int pil_q6v4_init_image(struct pil_desc *pil, const u8 *metadata,
79 size_t size)
80{
81 const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata;
82 struct q6v4_data *drv = dev_get_drvdata(pil->dev);
83 drv->start_addr = ehdr->e_entry;
84 return 0;
85}
86
87static int nop_verify_blob(struct pil_desc *pil, u32 phy_addr, size_t size)
88{
89 return 0;
90}
91
Stephen Boyded630b02012-01-26 15:26:47 -080092static int pil_q6v4_make_proxy_votes(struct device *dev)
Matt Wagantall39088932011-08-02 20:24:56 -070093{
94 struct q6v4_data *drv = dev_get_drvdata(dev);
Stephen Boydcc0f5342011-12-29 17:28:57 -080095 int ret;
Matt Wagantall39088932011-08-02 20:24:56 -070096
Stephen Boyded630b02012-01-26 15:26:47 -080097 ret = clk_prepare_enable(drv->xo);
98 if (ret) {
99 dev_err(dev, "Failed to enable XO\n");
100 goto err;
101 }
Stephen Boydcc0f5342011-12-29 17:28:57 -0800102 if (drv->pll_supply) {
103 ret = regulator_enable(drv->pll_supply);
Stephen Boyded630b02012-01-26 15:26:47 -0800104 if (ret) {
105 dev_err(dev, "Failed to enable pll supply\n");
106 goto err_regulator;
107 }
Stephen Boydcc0f5342011-12-29 17:28:57 -0800108 }
109 schedule_delayed_work(&drv->work, msecs_to_jiffies(PROXY_VOTE_TIMEOUT));
Stephen Boyded630b02012-01-26 15:26:47 -0800110 return 0;
111err_regulator:
112 clk_disable_unprepare(drv->xo);
113err:
114 return ret;
Matt Wagantall39088932011-08-02 20:24:56 -0700115}
116
Stephen Boydcc0f5342011-12-29 17:28:57 -0800117static void pil_q6v4_remove_proxy_votes(struct work_struct *work)
Matt Wagantall39088932011-08-02 20:24:56 -0700118{
Stephen Boydcc0f5342011-12-29 17:28:57 -0800119 struct q6v4_data *drv = container_of(work, struct q6v4_data, work.work);
120 if (drv->pll_supply)
121 regulator_disable(drv->pll_supply);
Stephen Boyded630b02012-01-26 15:26:47 -0800122 clk_disable_unprepare(drv->xo);
Matt Wagantall39088932011-08-02 20:24:56 -0700123}
124
Stephen Boydcc0f5342011-12-29 17:28:57 -0800125static void pil_q6v4_remove_proxy_votes_now(struct device *dev)
Matt Wagantall39088932011-08-02 20:24:56 -0700126{
127 struct q6v4_data *drv = dev_get_drvdata(dev);
Stephen Boydcc0f5342011-12-29 17:28:57 -0800128 flush_delayed_work(&drv->work);
Matt Wagantall39088932011-08-02 20:24:56 -0700129}
130
Stephen Boydeb819882011-08-29 14:46:30 -0700131static int pil_q6v4_power_up(struct device *dev)
132{
133 int err;
134 struct q6v4_data *drv = dev_get_drvdata(dev);
135
136 err = regulator_set_voltage(drv->vreg, 1050000, 1050000);
137 if (err) {
138 dev_err(dev, "Failed to set regulator's voltage.\n");
139 return err;
140 }
141 err = regulator_set_optimum_mode(drv->vreg, 100000);
142 if (err < 0) {
143 dev_err(dev, "Failed to set regulator's mode.\n");
144 return err;
145 }
146 err = regulator_enable(drv->vreg);
147 if (err) {
148 dev_err(dev, "Failed to enable regulator.\n");
149 return err;
150 }
151 drv->vreg_enabled = true;
152 return 0;
153}
154
155static DEFINE_MUTEX(pil_q6v4_modem_lock);
156static unsigned pil_q6v4_modem_count;
157
158/* Bring modem subsystem out of reset */
159static void pil_q6v4_init_modem(void __iomem *base, void __iomem *jtag_clk)
160{
161 mutex_lock(&pil_q6v4_modem_lock);
162 if (!pil_q6v4_modem_count) {
163 /* Enable MSS clocks */
164 writel_relaxed(0x10, SFAB_MSS_M_ACLK_CTL);
165 writel_relaxed(0x10, SFAB_MSS_S_HCLK_CTL);
166 writel_relaxed(0x10, MSS_S_HCLK_CTL);
167 writel_relaxed(0x10, MSS_SLP_CLK_CTL);
168 /* Wait for clocks to enable */
169 mb();
170 udelay(10);
171
172 /* De-assert MSS reset */
173 writel_relaxed(0x0, MSS_RESET);
174 mb();
175 udelay(10);
176 /* Enable MSS */
177 writel_relaxed(0x7, base);
178 }
179
180 /* Enable JTAG clocks */
181 /* TODO: Remove if/when Q6 software enables them? */
182 writel_relaxed(0x10, jtag_clk);
183
184 pil_q6v4_modem_count++;
185 mutex_unlock(&pil_q6v4_modem_lock);
186}
187
188/* Put modem subsystem back into reset */
189static void pil_q6v4_shutdown_modem(void)
190{
191 mutex_lock(&pil_q6v4_modem_lock);
192 if (pil_q6v4_modem_count)
193 pil_q6v4_modem_count--;
194 if (pil_q6v4_modem_count == 0)
195 writel_relaxed(0x1, MSS_RESET);
196 mutex_unlock(&pil_q6v4_modem_lock);
197}
198
199static int pil_q6v4_reset(struct pil_desc *pil)
200{
201 u32 reg, err = 0;
202 const struct q6v4_data *drv = dev_get_drvdata(pil->dev);
203 const struct pil_q6v4_pdata *pdata = pil->dev->platform_data;
204
Stephen Boyded630b02012-01-26 15:26:47 -0800205 err = pil_q6v4_make_proxy_votes(pil->dev);
206 if (err)
207 return err;
Matt Wagantall39088932011-08-02 20:24:56 -0700208
Stephen Boydeb819882011-08-29 14:46:30 -0700209 err = pil_q6v4_power_up(pil->dev);
210 if (err)
211 return err;
212 /* Enable Q6 ACLK */
213 writel_relaxed(0x10, pdata->aclk_reg);
214
215 if (drv->modem_base)
216 pil_q6v4_init_modem(drv->modem_base, pdata->jtag_clk_reg);
217
Matt Wagantall6e4aafb2011-09-09 17:53:54 -0700218 /* Unhalt bus port */
219 err = msm_bus_axi_portunhalt(pdata->bus_port);
220 if (err)
221 dev_err(pil->dev, "Failed to unhalt bus port\n");
222
Stephen Boydeb819882011-08-29 14:46:30 -0700223 /*
224 * Assert AXIS_ACLK_EN override to allow for correct updating of the
225 * QDSP6_CORE_STATE status bit. This is mandatory only for the SW Q6
226 * in 8960v1 and optional elsewhere.
227 */
228 reg = readl_relaxed(drv->base + QDSP6SS_CGC_OVERRIDE);
229 reg |= Q6SS_AXIS_ACLK_EN;
230 writel_relaxed(reg, drv->base + QDSP6SS_CGC_OVERRIDE);
231
232 /* Deassert Q6SS_SS_ARES */
233 reg = readl_relaxed(drv->base + QDSP6SS_RESET);
234 reg &= ~(Q6SS_SS_ARES);
235 writel_relaxed(reg, drv->base + QDSP6SS_RESET);
236
237 /* Program boot address */
238 writel_relaxed((drv->start_addr >> 8) & 0xFFFFFF,
239 drv->base + QDSP6SS_RST_EVB);
240
241 /* Program TCM and AHB address ranges */
242 writel_relaxed(pdata->strap_tcm_base, drv->base + QDSP6SS_STRAP_TCM);
243 writel_relaxed(pdata->strap_ahb_upper | pdata->strap_ahb_lower,
244 drv->base + QDSP6SS_STRAP_AHB);
245
246 /* Turn off Q6 core clock */
247 writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR,
248 drv->base + QDSP6SS_GFMUX_CTL);
249
250 /* Put memories to sleep */
251 writel_relaxed(Q6SS_CLAMP_IO, drv->base + QDSP6SS_PWR_CTL);
252
253 /* Assert resets */
254 reg = readl_relaxed(drv->base + QDSP6SS_RESET);
255 reg |= (Q6SS_CORE_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES
256 | Q6SS_STOP_CORE_ARES);
257 writel_relaxed(reg, drv->base + QDSP6SS_RESET);
258
259 /* Wait 8 AHB cycles for Q6 to be fully reset (AHB = 1.5Mhz) */
260 mb();
261 usleep_range(20, 30);
262
263 /* Turn on Q6 memories */
264 reg = Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | Q6SS_L1TCM_SLP_NRET_N
265 | Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLEEP_NRET_N | Q6SS_ARR_STBY_N
266 | Q6SS_CLAMP_IO;
267 writel_relaxed(reg, drv->base + QDSP6SS_PWR_CTL);
268
269 /* Turn on Q6 core clock */
270 reg = Q6SS_CLK_ENA | Q6SS_SRC_SWITCH_CLK_OVR;
271 writel_relaxed(reg, drv->base + QDSP6SS_GFMUX_CTL);
272
273 /* Remove Q6SS_CLAMP_IO */
274 reg = readl_relaxed(drv->base + QDSP6SS_PWR_CTL);
275 reg &= ~Q6SS_CLAMP_IO;
276 writel_relaxed(reg, drv->base + QDSP6SS_PWR_CTL);
277
278 /* Bring Q6 core out of reset and start execution. */
279 writel_relaxed(0x0, drv->base + QDSP6SS_RESET);
280
281 /*
282 * Re-enable auto-gating of AXIS_ACLK at lease one AXI clock cycle
283 * after resets are de-asserted.
284 */
285 mb();
286 usleep_range(1, 10);
287 reg = readl_relaxed(drv->base + QDSP6SS_CGC_OVERRIDE);
288 reg &= ~Q6SS_AXIS_ACLK_EN;
289 writel_relaxed(reg, drv->base + QDSP6SS_CGC_OVERRIDE);
290
291 return 0;
292}
293
294static int pil_q6v4_shutdown(struct pil_desc *pil)
295{
296 u32 reg;
297 struct q6v4_data *drv = dev_get_drvdata(pil->dev);
Matt Wagantall6e4aafb2011-09-09 17:53:54 -0700298 const struct pil_q6v4_pdata *pdata = pil->dev->platform_data;
299
300 /* Make sure bus port is halted */
301 msm_bus_axi_porthalt(pdata->bus_port);
Stephen Boydeb819882011-08-29 14:46:30 -0700302
303 /* Turn off Q6 core clock */
304 writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR,
305 drv->base + QDSP6SS_GFMUX_CTL);
306
307 /* Assert resets */
308 reg = (Q6SS_SS_ARES | Q6SS_CORE_ARES | Q6SS_ISDB_ARES
309 | Q6SS_ETM_ARES | Q6SS_STOP_CORE_ARES | Q6SS_PRIV_ARES);
310 writel_relaxed(reg, drv->base + QDSP6SS_RESET);
311
312 /* Turn off Q6 memories */
313 writel_relaxed(Q6SS_CLAMP_IO, drv->base + QDSP6SS_PWR_CTL);
314
315 if (drv->modem_base)
316 pil_q6v4_shutdown_modem();
317
318 if (drv->vreg_enabled) {
319 regulator_disable(drv->vreg);
320 drv->vreg_enabled = false;
321 }
322
Stephen Boydcc0f5342011-12-29 17:28:57 -0800323 pil_q6v4_remove_proxy_votes_now(pil->dev);
Matt Wagantall39088932011-08-02 20:24:56 -0700324
Stephen Boydeb819882011-08-29 14:46:30 -0700325 return 0;
326}
327
328static struct pil_reset_ops pil_q6v4_ops = {
329 .init_image = pil_q6v4_init_image,
330 .verify_blob = nop_verify_blob,
331 .auth_and_reset = pil_q6v4_reset,
332 .shutdown = pil_q6v4_shutdown,
333};
334
335static int pil_q6v4_init_image_trusted(struct pil_desc *pil,
336 const u8 *metadata, size_t size)
337{
338 const struct pil_q6v4_pdata *pdata = pil->dev->platform_data;
339 return pas_init_image(pdata->pas_id, metadata, size);
340}
341
342static int pil_q6v4_reset_trusted(struct pil_desc *pil)
343{
344 const struct pil_q6v4_pdata *pdata = pil->dev->platform_data;
345 int err;
346
Stephen Boyded630b02012-01-26 15:26:47 -0800347 err = pil_q6v4_make_proxy_votes(pil->dev);
348 if (err)
349 return err;
Matt Wagantall39088932011-08-02 20:24:56 -0700350
Stephen Boydeb819882011-08-29 14:46:30 -0700351 err = pil_q6v4_power_up(pil->dev);
352 if (err)
353 return err;
Matt Wagantall6e4aafb2011-09-09 17:53:54 -0700354
355 /* Unhalt bus port */
356 err = msm_bus_axi_portunhalt(pdata->bus_port);
357 if (err)
358 dev_err(pil->dev, "Failed to unhalt bus port\n");
Stephen Boydeb819882011-08-29 14:46:30 -0700359 return pas_auth_and_reset(pdata->pas_id);
360}
361
362static int pil_q6v4_shutdown_trusted(struct pil_desc *pil)
363{
364 int ret;
365 struct q6v4_data *drv = dev_get_drvdata(pil->dev);
366 struct pil_q6v4_pdata *pdata = pil->dev->platform_data;
367
Matt Wagantall6e4aafb2011-09-09 17:53:54 -0700368 /* Make sure bus port is halted */
369 msm_bus_axi_porthalt(pdata->bus_port);
370
Stephen Boydeb819882011-08-29 14:46:30 -0700371 ret = pas_shutdown(pdata->pas_id);
372 if (ret)
373 return ret;
374
375 if (drv->vreg_enabled) {
376 regulator_disable(drv->vreg);
377 drv->vreg_enabled = false;
378 }
379
Stephen Boydcc0f5342011-12-29 17:28:57 -0800380 pil_q6v4_remove_proxy_votes_now(pil->dev);
Matt Wagantall39088932011-08-02 20:24:56 -0700381
Stephen Boydeb819882011-08-29 14:46:30 -0700382 return ret;
383}
384
385static struct pil_reset_ops pil_q6v4_ops_trusted = {
386 .init_image = pil_q6v4_init_image_trusted,
387 .verify_blob = nop_verify_blob,
388 .auth_and_reset = pil_q6v4_reset_trusted,
389 .shutdown = pil_q6v4_shutdown_trusted,
390};
391
392static int __devinit pil_q6v4_driver_probe(struct platform_device *pdev)
393{
394 const struct pil_q6v4_pdata *pdata = pdev->dev.platform_data;
395 struct q6v4_data *drv;
396 struct resource *res;
397 struct pil_desc *desc;
Stephen Boydcc0f5342011-12-29 17:28:57 -0800398 int ret;
Stephen Boydeb819882011-08-29 14:46:30 -0700399
400 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
401 if (!res)
402 return -EINVAL;
403
404 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
405 if (!drv)
406 return -ENOMEM;
407 platform_set_drvdata(pdev, drv);
408
409 drv->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
410 if (!drv->base)
411 return -ENOMEM;
412
413 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
414 if (res) {
415 drv->modem_base = devm_ioremap(&pdev->dev, res->start,
416 resource_size(res));
417 if (!drv->modem_base)
418 return -ENOMEM;
419 }
420
421 desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
Stephen Boydcc0f5342011-12-29 17:28:57 -0800422 if (!desc)
Stephen Boydeb819882011-08-29 14:46:30 -0700423 return -ENOMEM;
424
Stephen Boydcc0f5342011-12-29 17:28:57 -0800425 drv->pll_supply = regulator_get(&pdev->dev, "pll_vdd");
426 if (IS_ERR(drv->pll_supply)) {
427 drv->pll_supply = NULL;
428 } else {
429 ret = regulator_set_voltage(drv->pll_supply, 1800000, 1800000);
430 if (ret) {
431 dev_err(&pdev->dev, "failed to set pll voltage\n");
432 goto err;
433 }
434
435 ret = regulator_set_optimum_mode(drv->pll_supply, 100000);
436 if (ret < 0) {
437 dev_err(&pdev->dev, "failed to set pll optimum mode\n");
438 goto err;
439 }
440 }
441
Stephen Boydeb819882011-08-29 14:46:30 -0700442 desc->name = pdata->name;
443 desc->depends_on = pdata->depends;
444 desc->dev = &pdev->dev;
445
446 if (pas_supported(pdata->pas_id) > 0) {
447 desc->ops = &pil_q6v4_ops_trusted;
448 dev_info(&pdev->dev, "using secure boot\n");
449 } else {
450 desc->ops = &pil_q6v4_ops;
451 dev_info(&pdev->dev, "using non-secure boot\n");
452 }
453
454 drv->vreg = regulator_get(&pdev->dev, "core_vdd");
Stephen Boydcc0f5342011-12-29 17:28:57 -0800455 if (IS_ERR(drv->vreg)) {
456 ret = PTR_ERR(drv->vreg);
457 goto err;
Stephen Boydeb819882011-08-29 14:46:30 -0700458 }
Stephen Boydcc0f5342011-12-29 17:28:57 -0800459
Stephen Boyded630b02012-01-26 15:26:47 -0800460 drv->xo = clk_get(&pdev->dev, "xo");
Stephen Boydcc0f5342011-12-29 17:28:57 -0800461 if (IS_ERR(drv->xo)) {
462 ret = PTR_ERR(drv->xo);
463 goto err_xo;
464 }
465 INIT_DELAYED_WORK(&drv->work, pil_q6v4_remove_proxy_votes);
466
467 ret = msm_pil_register(desc);
468 if (ret)
469 goto err_pil;
Stephen Boydeb819882011-08-29 14:46:30 -0700470 return 0;
Stephen Boydcc0f5342011-12-29 17:28:57 -0800471err_pil:
Stephen Boyd7307ffb2012-01-30 17:03:42 -0800472 flush_delayed_work_sync(&drv->work);
Stephen Boyded630b02012-01-26 15:26:47 -0800473 clk_put(drv->xo);
Stephen Boydcc0f5342011-12-29 17:28:57 -0800474err_xo:
475 regulator_put(drv->vreg);
476err:
477 regulator_put(drv->pll_supply);
478 return ret;
Stephen Boydeb819882011-08-29 14:46:30 -0700479}
480
481static int __devexit pil_q6v4_driver_exit(struct platform_device *pdev)
482{
483 struct q6v4_data *drv = platform_get_drvdata(pdev);
Stephen Boyd7307ffb2012-01-30 17:03:42 -0800484 flush_delayed_work_sync(&drv->work);
Stephen Boyded630b02012-01-26 15:26:47 -0800485 clk_put(drv->xo);
Stephen Boydeb819882011-08-29 14:46:30 -0700486 regulator_put(drv->vreg);
Stephen Boydcc0f5342011-12-29 17:28:57 -0800487 regulator_put(drv->pll_supply);
Stephen Boydeb819882011-08-29 14:46:30 -0700488 return 0;
489}
490
491static struct platform_driver pil_q6v4_driver = {
492 .probe = pil_q6v4_driver_probe,
493 .remove = __devexit_p(pil_q6v4_driver_exit),
494 .driver = {
495 .name = "pil_qdsp6v4",
496 .owner = THIS_MODULE,
497 },
498};
499
500static int __init pil_q6v4_init(void)
501{
502 return platform_driver_register(&pil_q6v4_driver);
503}
504module_init(pil_q6v4_init);
505
506static void __exit pil_q6v4_exit(void)
507{
508 platform_driver_unregister(&pil_q6v4_driver);
509}
510module_exit(pil_q6v4_exit);
511
512MODULE_DESCRIPTION("Support for booting QDSP6v4 (Hexagon) processors");
513MODULE_LICENSE("GPL v2");