Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2012, Code Aurora Forum. All rights reserved. |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License version 2 and |
| 6 | * only version 2 as published by the Free Software Foundation. |
| 7 | * |
| 8 | * This program is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | */ |
| 13 | |
| 14 | #include <linux/init.h> |
| 15 | #include <linux/module.h> |
| 16 | #include <linux/platform_device.h> |
| 17 | #include <linux/io.h> |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 18 | #include <linux/err.h> |
| 19 | #include <linux/of.h> |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 20 | #include <linux/clk.h> |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 21 | #include <linux/workqueue.h> |
| 22 | #include <linux/interrupt.h> |
| 23 | #include <linux/delay.h> |
| 24 | |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 25 | #include <mach/clk.h> |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 26 | #include <mach/subsystem_restart.h> |
| 27 | #include <mach/subsystem_notif.h> |
| 28 | #include <mach/scm.h> |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 29 | |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 30 | #include "peripheral-loader.h" |
| 31 | #include "pil-q6v5.h" |
Matt Wagantall | 10a5967 | 2012-07-26 11:52:02 -0700 | [diff] [blame] | 32 | #include "scm-pas.h" |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 33 | #include "ramdump.h" |
| 34 | #include "sysmon.h" |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 35 | |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 36 | #define QDSP6SS_RST_EVB 0x010 |
Matt Wagantall | 4d89c2e | 2012-05-25 19:28:34 -0700 | [diff] [blame] | 37 | #define PROXY_TIMEOUT_MS 10000 |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 38 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 39 | struct lpass_data { |
| 40 | struct q6v5_data *q6; |
| 41 | struct subsys_device *subsys; |
| 42 | struct subsys_desc subsys_desc; |
| 43 | void *ramdump_dev; |
| 44 | int wdog_irq; |
| 45 | struct work_struct work; |
| 46 | void *riva_notif_hdle; |
| 47 | void *modem_notif_hdle; |
| 48 | int crash_shutdown; |
| 49 | }; |
| 50 | |
| 51 | #define subsys_to_drv(d) container_of(d, struct lpass_data, subsys_desc) |
| 52 | |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 53 | static int pil_lpass_enable_clks(struct q6v5_data *drv) |
| 54 | { |
| 55 | int ret; |
| 56 | |
| 57 | ret = clk_reset(drv->core_clk, CLK_RESET_DEASSERT); |
| 58 | if (ret) |
| 59 | goto err_reset; |
| 60 | ret = clk_prepare_enable(drv->core_clk); |
| 61 | if (ret) |
| 62 | goto err_core_clk; |
| 63 | ret = clk_prepare_enable(drv->ahb_clk); |
| 64 | if (ret) |
| 65 | goto err_ahb_clk; |
| 66 | ret = clk_prepare_enable(drv->axi_clk); |
| 67 | if (ret) |
| 68 | goto err_axi_clk; |
| 69 | ret = clk_prepare_enable(drv->reg_clk); |
| 70 | if (ret) |
| 71 | goto err_reg_clk; |
| 72 | |
| 73 | return 0; |
| 74 | |
| 75 | err_reg_clk: |
| 76 | clk_disable_unprepare(drv->axi_clk); |
| 77 | err_axi_clk: |
| 78 | clk_disable_unprepare(drv->ahb_clk); |
| 79 | err_ahb_clk: |
| 80 | clk_disable_unprepare(drv->core_clk); |
| 81 | err_core_clk: |
| 82 | clk_reset(drv->core_clk, CLK_RESET_ASSERT); |
| 83 | err_reset: |
| 84 | return ret; |
| 85 | } |
| 86 | |
| 87 | static void pil_lpass_disable_clks(struct q6v5_data *drv) |
| 88 | { |
| 89 | clk_disable_unprepare(drv->reg_clk); |
| 90 | clk_disable_unprepare(drv->axi_clk); |
| 91 | clk_disable_unprepare(drv->ahb_clk); |
| 92 | clk_disable_unprepare(drv->core_clk); |
| 93 | clk_reset(drv->core_clk, CLK_RESET_ASSERT); |
| 94 | } |
| 95 | |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 96 | static int pil_lpass_shutdown(struct pil_desc *pil) |
| 97 | { |
Stephen Boyd | 3826cd4 | 2012-07-05 17:37:53 -0700 | [diff] [blame] | 98 | struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 99 | |
Matt Wagantall | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 100 | pil_q6v5_halt_axi_port(pil, drv->axi_halt_base); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 101 | |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 102 | /* |
| 103 | * If the shutdown function is called before the reset function, clocks |
| 104 | * will not be enabled yet. Enable them here so that register writes |
| 105 | * performed during the shutdown succeed. |
| 106 | */ |
| 107 | if (drv->is_booted == false) |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 108 | pil_lpass_enable_clks(drv); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 109 | |
| 110 | pil_q6v5_shutdown(pil); |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 111 | pil_lpass_disable_clks(drv); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 112 | |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 113 | drv->is_booted = false; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 114 | |
| 115 | return 0; |
| 116 | } |
| 117 | |
| 118 | static int pil_lpass_reset(struct pil_desc *pil) |
| 119 | { |
Stephen Boyd | 3826cd4 | 2012-07-05 17:37:53 -0700 | [diff] [blame] | 120 | struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); |
Stephen Boyd | 3030c25 | 2012-08-08 17:24:05 -0700 | [diff] [blame] | 121 | unsigned long start_addr = pil_get_entry_addr(pil); |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 122 | int ret; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 123 | |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 124 | ret = pil_lpass_enable_clks(drv); |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 125 | if (ret) |
| 126 | return ret; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 127 | |
| 128 | /* Program Image Address */ |
Stephen Boyd | 3030c25 | 2012-08-08 17:24:05 -0700 | [diff] [blame] | 129 | writel_relaxed((start_addr >> 4) & 0x0FFFFFF0, |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 130 | drv->reg_base + QDSP6SS_RST_EVB); |
| 131 | |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 132 | ret = pil_q6v5_reset(pil); |
| 133 | if (ret) { |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 134 | pil_lpass_disable_clks(drv); |
Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 135 | return ret; |
| 136 | } |
| 137 | |
| 138 | drv->is_booted = true; |
| 139 | |
| 140 | return 0; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | static struct pil_reset_ops pil_lpass_ops = { |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 144 | .proxy_vote = pil_q6v5_make_proxy_votes, |
| 145 | .proxy_unvote = pil_q6v5_remove_proxy_votes, |
| 146 | .auth_and_reset = pil_lpass_reset, |
| 147 | .shutdown = pil_lpass_shutdown, |
| 148 | }; |
| 149 | |
Matt Wagantall | 10a5967 | 2012-07-26 11:52:02 -0700 | [diff] [blame] | 150 | static int pil_lpass_init_image_trusted(struct pil_desc *pil, |
| 151 | const u8 *metadata, size_t size) |
| 152 | { |
| 153 | return pas_init_image(PAS_Q6, metadata, size); |
| 154 | } |
| 155 | |
| 156 | static int pil_lpass_reset_trusted(struct pil_desc *pil) |
| 157 | { |
| 158 | return pas_auth_and_reset(PAS_Q6); |
| 159 | } |
| 160 | |
| 161 | static int pil_lpass_shutdown_trusted(struct pil_desc *pil) |
| 162 | { |
| 163 | return pas_shutdown(PAS_Q6); |
| 164 | } |
| 165 | |
| 166 | static struct pil_reset_ops pil_lpass_ops_trusted = { |
| 167 | .init_image = pil_lpass_init_image_trusted, |
| 168 | .proxy_vote = pil_q6v5_make_proxy_votes, |
| 169 | .proxy_unvote = pil_q6v5_remove_proxy_votes, |
| 170 | .auth_and_reset = pil_lpass_reset_trusted, |
| 171 | .shutdown = pil_lpass_shutdown_trusted, |
| 172 | }; |
| 173 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 174 | static int riva_notifier_cb(struct notifier_block *this, unsigned long code, |
| 175 | void *ss_handle) |
| 176 | { |
| 177 | int ret; |
| 178 | switch (code) { |
| 179 | case SUBSYS_BEFORE_SHUTDOWN: |
| 180 | pr_debug("%s: R-Notify: Shutdown started\n", __func__); |
| 181 | ret = sysmon_send_event(SYSMON_SS_LPASS, "wcnss", |
| 182 | SUBSYS_BEFORE_SHUTDOWN); |
| 183 | if (ret < 0) |
| 184 | pr_err("%s: sysmon_send_event error %d", __func__, ret); |
| 185 | break; |
| 186 | } |
| 187 | return NOTIFY_DONE; |
| 188 | } |
| 189 | |
| 190 | static struct notifier_block rnb = { |
| 191 | .notifier_call = riva_notifier_cb, |
| 192 | }; |
| 193 | |
| 194 | static int modem_notifier_cb(struct notifier_block *this, unsigned long code, |
| 195 | void *ss_handle) |
| 196 | { |
| 197 | int ret; |
Ravishankar Sarawadi | fadfe3b | 2012-10-12 11:28:24 -0700 | [diff] [blame] | 198 | pr_debug("%s: M-Notify: event %lu\n", __func__, code); |
| 199 | ret = sysmon_send_event(SYSMON_SS_LPASS, "modem", code); |
| 200 | if (ret < 0) |
| 201 | pr_err("%s: sysmon_send_event error %d", __func__, ret); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 202 | return NOTIFY_DONE; |
| 203 | } |
| 204 | |
| 205 | static struct notifier_block mnb = { |
| 206 | .notifier_call = modem_notifier_cb, |
| 207 | }; |
| 208 | |
| 209 | static void adsp_log_failure_reason(void) |
| 210 | { |
| 211 | char *reason; |
| 212 | char buffer[81]; |
| 213 | unsigned size; |
| 214 | |
| 215 | reason = smem_get_entry(SMEM_SSR_REASON_LPASS0, &size); |
| 216 | |
| 217 | if (!reason) { |
| 218 | pr_err("ADSP subsystem failure reason: (unknown, smem_get_entry failed)."); |
| 219 | return; |
| 220 | } |
| 221 | |
| 222 | if (reason[0] == '\0') { |
| 223 | pr_err("ADSP subsystem failure reason: (unknown, init value found)"); |
| 224 | return; |
| 225 | } |
| 226 | |
| 227 | size = min(size, sizeof(buffer) - 1); |
| 228 | memcpy(buffer, reason, size); |
| 229 | buffer[size] = '\0'; |
| 230 | pr_err("ADSP subsystem failure reason: %s", buffer); |
| 231 | memset((void *)reason, 0x0, size); |
| 232 | wmb(); |
| 233 | } |
| 234 | |
| 235 | static void restart_adsp(struct lpass_data *drv) |
| 236 | { |
| 237 | adsp_log_failure_reason(); |
| 238 | subsystem_restart_dev(drv->subsys); |
| 239 | } |
| 240 | |
| 241 | static void adsp_fatal_fn(struct work_struct *work) |
| 242 | { |
| 243 | struct lpass_data *drv = container_of(work, struct lpass_data, work); |
| 244 | |
| 245 | pr_err("Watchdog bite received from ADSP!\n"); |
| 246 | restart_adsp(drv); |
| 247 | } |
| 248 | |
| 249 | static void adsp_smsm_state_cb(void *data, uint32_t old_state, |
| 250 | uint32_t new_state) |
| 251 | { |
| 252 | struct lpass_data *drv = data; |
| 253 | |
| 254 | /* Ignore if we're the one that set SMSM_RESET */ |
| 255 | if (drv->crash_shutdown) |
| 256 | return; |
| 257 | |
| 258 | if (new_state & SMSM_RESET) { |
| 259 | pr_err("%s: ADSP SMSM state changed to SMSM_RESET, new_state = %#x, old_state = %#x\n", |
| 260 | __func__, new_state, old_state); |
| 261 | restart_adsp(drv); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | #define SCM_Q6_NMI_CMD 0x1 |
| 266 | |
| 267 | static void send_q6_nmi(void) |
| 268 | { |
| 269 | /* Send NMI to QDSP6 via an SCM call. */ |
| 270 | scm_call_atomic1(SCM_SVC_UTIL, SCM_Q6_NMI_CMD, 0x1); |
| 271 | pr_debug("%s: Q6 NMI was sent.\n", __func__); |
| 272 | } |
| 273 | |
| 274 | #define subsys_to_lpass(d) container_of(d, struct lpass_data, subsys_desc) |
| 275 | |
| 276 | static int adsp_shutdown(const struct subsys_desc *subsys) |
| 277 | { |
| 278 | struct lpass_data *drv = subsys_to_lpass(subsys); |
| 279 | |
| 280 | send_q6_nmi(); |
| 281 | /* The write needs to go through before the q6 is shutdown. */ |
| 282 | mb(); |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 283 | pil_shutdown(&drv->q6->desc); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 284 | disable_irq_nosync(drv->wdog_irq); |
| 285 | |
| 286 | return 0; |
| 287 | } |
| 288 | |
| 289 | static int adsp_powerup(const struct subsys_desc *subsys) |
| 290 | { |
| 291 | struct lpass_data *drv = subsys_to_lpass(subsys); |
| 292 | int ret = 0; |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 293 | ret = pil_boot(&drv->q6->desc); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 294 | enable_irq(drv->wdog_irq); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 295 | return ret; |
| 296 | } |
| 297 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 298 | static int adsp_ramdump(int enable, const struct subsys_desc *subsys) |
| 299 | { |
| 300 | struct lpass_data *drv = subsys_to_lpass(subsys); |
| 301 | |
| 302 | if (!enable) |
| 303 | return 0; |
Stephen Boyd | 5eb17ce | 2012-11-29 15:34:21 -0800 | [diff] [blame] | 304 | |
| 305 | return pil_do_ramdump(&drv->q6->desc, drv->ramdump_dev); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 306 | } |
| 307 | |
| 308 | static void adsp_crash_shutdown(const struct subsys_desc *subsys) |
| 309 | { |
| 310 | struct lpass_data *drv = subsys_to_lpass(subsys); |
| 311 | |
| 312 | drv->crash_shutdown = 1; |
| 313 | send_q6_nmi(); |
| 314 | } |
| 315 | |
| 316 | static irqreturn_t adsp_wdog_bite_irq(int irq, void *dev_id) |
| 317 | { |
| 318 | struct lpass_data *drv = dev_id; |
| 319 | |
| 320 | disable_irq_nosync(drv->wdog_irq); |
| 321 | schedule_work(&drv->work); |
| 322 | |
| 323 | return IRQ_HANDLED; |
| 324 | } |
| 325 | |
Stephen Boyd | 3e4e975 | 2012-06-27 12:46:32 -0700 | [diff] [blame] | 326 | static int lpass_start(const struct subsys_desc *desc) |
| 327 | { |
Stephen Boyd | 3e4e975 | 2012-06-27 12:46:32 -0700 | [diff] [blame] | 328 | struct lpass_data *drv = subsys_to_drv(desc); |
| 329 | |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 330 | return pil_boot(&drv->q6->desc); |
Stephen Boyd | 3e4e975 | 2012-06-27 12:46:32 -0700 | [diff] [blame] | 331 | } |
| 332 | |
| 333 | static void lpass_stop(const struct subsys_desc *desc) |
| 334 | { |
| 335 | struct lpass_data *drv = subsys_to_drv(desc); |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 336 | pil_shutdown(&drv->q6->desc); |
Stephen Boyd | 3e4e975 | 2012-06-27 12:46:32 -0700 | [diff] [blame] | 337 | } |
| 338 | |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 339 | static int __devinit pil_lpass_driver_probe(struct platform_device *pdev) |
| 340 | { |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 341 | struct lpass_data *drv; |
| 342 | struct q6v5_data *q6; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 343 | struct pil_desc *desc; |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 344 | int ret; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 345 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 346 | drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); |
| 347 | if (!drv) |
| 348 | return -ENOMEM; |
Stephen Boyd | 3826cd4 | 2012-07-05 17:37:53 -0700 | [diff] [blame] | 349 | platform_set_drvdata(pdev, drv); |
Matt Wagantall | 55252f1 | 2012-05-02 18:02:54 -0700 | [diff] [blame] | 350 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 351 | drv->wdog_irq = platform_get_irq(pdev, 0); |
| 352 | if (drv->wdog_irq < 0) |
| 353 | return drv->wdog_irq; |
| 354 | |
| 355 | q6 = pil_q6v5_init(pdev); |
| 356 | if (IS_ERR(q6)) |
| 357 | return PTR_ERR(q6); |
| 358 | drv->q6 = q6; |
| 359 | |
| 360 | desc = &q6->desc; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 361 | desc->owner = THIS_MODULE; |
Matt Wagantall | 4d89c2e | 2012-05-25 19:28:34 -0700 | [diff] [blame] | 362 | desc->proxy_timeout = PROXY_TIMEOUT_MS; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 363 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 364 | q6->core_clk = devm_clk_get(&pdev->dev, "core_clk"); |
| 365 | if (IS_ERR(q6->core_clk)) |
| 366 | return PTR_ERR(q6->core_clk); |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 367 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 368 | q6->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk"); |
| 369 | if (IS_ERR(q6->ahb_clk)) |
| 370 | return PTR_ERR(q6->ahb_clk); |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 371 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 372 | q6->axi_clk = devm_clk_get(&pdev->dev, "bus_clk"); |
| 373 | if (IS_ERR(q6->axi_clk)) |
| 374 | return PTR_ERR(q6->axi_clk); |
Matt Wagantall | 8c2246d | 2012-08-12 17:08:04 -0700 | [diff] [blame] | 375 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 376 | q6->reg_clk = devm_clk_get(&pdev->dev, "reg_clk"); |
| 377 | if (IS_ERR(q6->reg_clk)) |
| 378 | return PTR_ERR(q6->reg_clk); |
Matt Wagantall | 56865f0 | 2012-08-09 15:03:36 -0700 | [diff] [blame] | 379 | |
Matt Wagantall | 10a5967 | 2012-07-26 11:52:02 -0700 | [diff] [blame] | 380 | if (pas_supported(PAS_Q6) > 0) { |
| 381 | desc->ops = &pil_lpass_ops_trusted; |
| 382 | dev_info(&pdev->dev, "using secure boot\n"); |
| 383 | } else { |
| 384 | desc->ops = &pil_lpass_ops; |
| 385 | dev_info(&pdev->dev, "using non-secure boot\n"); |
| 386 | } |
| 387 | |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 388 | ret = pil_desc_init(desc); |
| 389 | if (ret) |
| 390 | return ret; |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 391 | |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 392 | drv->subsys_desc.name = desc->name; |
| 393 | drv->subsys_desc.owner = THIS_MODULE; |
| 394 | drv->subsys_desc.dev = &pdev->dev; |
| 395 | drv->subsys_desc.shutdown = adsp_shutdown; |
| 396 | drv->subsys_desc.powerup = adsp_powerup; |
| 397 | drv->subsys_desc.ramdump = adsp_ramdump; |
| 398 | drv->subsys_desc.crash_shutdown = adsp_crash_shutdown; |
Stephen Boyd | 3e4e975 | 2012-06-27 12:46:32 -0700 | [diff] [blame] | 399 | drv->subsys_desc.start = lpass_start; |
| 400 | drv->subsys_desc.stop = lpass_stop; |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 401 | |
| 402 | INIT_WORK(&drv->work, adsp_fatal_fn); |
| 403 | |
Stephen Boyd | c1a7261 | 2012-07-05 14:07:35 -0700 | [diff] [blame] | 404 | drv->ramdump_dev = create_ramdump_device("adsp", &pdev->dev); |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 405 | if (!drv->ramdump_dev) { |
| 406 | ret = -ENOMEM; |
| 407 | goto err_ramdump; |
| 408 | } |
| 409 | |
| 410 | drv->subsys = subsys_register(&drv->subsys_desc); |
| 411 | if (IS_ERR(drv->subsys)) { |
| 412 | ret = PTR_ERR(drv->subsys); |
| 413 | goto err_subsys; |
| 414 | } |
| 415 | |
| 416 | ret = devm_request_irq(&pdev->dev, drv->wdog_irq, adsp_wdog_bite_irq, |
| 417 | IRQF_TRIGGER_RISING, dev_name(&pdev->dev), drv); |
| 418 | if (ret) |
| 419 | goto err_irq; |
| 420 | |
| 421 | ret = smsm_state_cb_register(SMSM_Q6_STATE, SMSM_RESET, |
| 422 | adsp_smsm_state_cb, drv); |
| 423 | if (ret < 0) |
| 424 | goto err_smsm; |
| 425 | |
| 426 | drv->riva_notif_hdle = subsys_notif_register_notifier("riva", &rnb); |
| 427 | if (IS_ERR(drv->riva_notif_hdle)) { |
| 428 | ret = PTR_ERR(drv->riva_notif_hdle); |
| 429 | goto err_notif_riva; |
| 430 | } |
| 431 | |
| 432 | drv->modem_notif_hdle = subsys_notif_register_notifier("modem", &mnb); |
| 433 | if (IS_ERR(drv->modem_notif_hdle)) { |
| 434 | ret = PTR_ERR(drv->modem_notif_hdle); |
| 435 | goto err_notif_modem; |
| 436 | } |
| 437 | return 0; |
| 438 | err_notif_modem: |
| 439 | subsys_notif_unregister_notifier(drv->riva_notif_hdle, &rnb); |
| 440 | err_notif_riva: |
| 441 | smsm_state_cb_deregister(SMSM_Q6_STATE, SMSM_RESET, |
| 442 | adsp_smsm_state_cb, drv); |
| 443 | err_smsm: |
| 444 | err_irq: |
| 445 | subsys_unregister(drv->subsys); |
| 446 | err_subsys: |
| 447 | destroy_ramdump_device(drv->ramdump_dev); |
| 448 | err_ramdump: |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 449 | pil_desc_release(desc); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 450 | return 0; |
| 451 | } |
| 452 | |
| 453 | static int __devexit pil_lpass_driver_exit(struct platform_device *pdev) |
| 454 | { |
Stephen Boyd | 633eb62 | 2012-06-13 12:05:35 -0700 | [diff] [blame] | 455 | struct lpass_data *drv = platform_get_drvdata(pdev); |
| 456 | subsys_notif_unregister_notifier(drv->riva_notif_hdle, &rnb); |
| 457 | subsys_notif_unregister_notifier(drv->modem_notif_hdle, &mnb); |
| 458 | smsm_state_cb_deregister(SMSM_Q6_STATE, SMSM_RESET, |
| 459 | adsp_smsm_state_cb, drv); |
| 460 | subsys_unregister(drv->subsys); |
| 461 | destroy_ramdump_device(drv->ramdump_dev); |
Stephen Boyd | e83a0a2 | 2012-06-29 13:51:27 -0700 | [diff] [blame] | 462 | pil_desc_release(&drv->q6->desc); |
Matt Wagantall | c2bbdc3 | 2012-03-21 19:44:50 -0700 | [diff] [blame] | 463 | return 0; |
| 464 | } |
| 465 | |
| 466 | static struct of_device_id lpass_match_table[] = { |
| 467 | { .compatible = "qcom,pil-q6v5-lpass" }, |
| 468 | {} |
| 469 | }; |
| 470 | |
| 471 | static struct platform_driver pil_lpass_driver = { |
| 472 | .probe = pil_lpass_driver_probe, |
| 473 | .remove = __devexit_p(pil_lpass_driver_exit), |
| 474 | .driver = { |
| 475 | .name = "pil-q6v5-lpass", |
| 476 | .of_match_table = lpass_match_table, |
| 477 | .owner = THIS_MODULE, |
| 478 | }, |
| 479 | }; |
| 480 | |
| 481 | static int __init pil_lpass_init(void) |
| 482 | { |
| 483 | return platform_driver_register(&pil_lpass_driver); |
| 484 | } |
| 485 | module_init(pil_lpass_init); |
| 486 | |
| 487 | static void __exit pil_lpass_exit(void) |
| 488 | { |
| 489 | platform_driver_unregister(&pil_lpass_driver); |
| 490 | } |
| 491 | module_exit(pil_lpass_exit); |
| 492 | |
| 493 | MODULE_DESCRIPTION("Support for booting low-power audio subsystems with QDSP6v5 (Hexagon) processors"); |
| 494 | MODULE_LICENSE("GPL v2"); |