blob: 44d8bc67476d8b9f3ecb486f6c36fba968f92f6f [file] [log] [blame]
Matt Wagantallc2bbdc32012-03-21 19:44:50 -07001/*
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 Wagantallc2bbdc32012-03-21 19:44:50 -070018#include <linux/err.h>
19#include <linux/of.h>
Matt Wagantalld41ce772012-05-10 23:16:41 -070020#include <linux/clk.h>
Matt Wagantall8c2246d2012-08-12 17:08:04 -070021#include <mach/clk.h>
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070022#include "peripheral-loader.h"
23#include "pil-q6v5.h"
24
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070025#define QDSP6SS_RST_EVB 0x010
Matt Wagantall4d89c2e2012-05-25 19:28:34 -070026#define PROXY_TIMEOUT_MS 10000
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070027
Matt Wagantall8c2246d2012-08-12 17:08:04 -070028static int pil_lpass_enable_clks(struct q6v5_data *drv)
29{
30 int ret;
31
32 ret = clk_reset(drv->core_clk, CLK_RESET_DEASSERT);
33 if (ret)
34 goto err_reset;
35 ret = clk_prepare_enable(drv->core_clk);
36 if (ret)
37 goto err_core_clk;
38 ret = clk_prepare_enable(drv->ahb_clk);
39 if (ret)
40 goto err_ahb_clk;
41 ret = clk_prepare_enable(drv->axi_clk);
42 if (ret)
43 goto err_axi_clk;
44 ret = clk_prepare_enable(drv->reg_clk);
45 if (ret)
46 goto err_reg_clk;
47
48 return 0;
49
50err_reg_clk:
51 clk_disable_unprepare(drv->axi_clk);
52err_axi_clk:
53 clk_disable_unprepare(drv->ahb_clk);
54err_ahb_clk:
55 clk_disable_unprepare(drv->core_clk);
56err_core_clk:
57 clk_reset(drv->core_clk, CLK_RESET_ASSERT);
58err_reset:
59 return ret;
60}
61
62static void pil_lpass_disable_clks(struct q6v5_data *drv)
63{
64 clk_disable_unprepare(drv->reg_clk);
65 clk_disable_unprepare(drv->axi_clk);
66 clk_disable_unprepare(drv->ahb_clk);
67 clk_disable_unprepare(drv->core_clk);
68 clk_reset(drv->core_clk, CLK_RESET_ASSERT);
69}
70
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070071static int pil_lpass_shutdown(struct pil_desc *pil)
72{
73 struct q6v5_data *drv = dev_get_drvdata(pil->dev);
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070074
Matt Wagantallb7747992012-05-11 19:37:51 -070075 pil_q6v5_halt_axi_port(pil, drv->axi_halt_base);
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070076
Matt Wagantalld41ce772012-05-10 23:16:41 -070077 /*
78 * If the shutdown function is called before the reset function, clocks
79 * will not be enabled yet. Enable them here so that register writes
80 * performed during the shutdown succeed.
81 */
82 if (drv->is_booted == false)
Matt Wagantall8c2246d2012-08-12 17:08:04 -070083 pil_lpass_enable_clks(drv);
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070084
85 pil_q6v5_shutdown(pil);
Matt Wagantall8c2246d2012-08-12 17:08:04 -070086 pil_lpass_disable_clks(drv);
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070087
Matt Wagantalld41ce772012-05-10 23:16:41 -070088 drv->is_booted = false;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070089
90 return 0;
91}
92
93static int pil_lpass_reset(struct pil_desc *pil)
94{
95 struct q6v5_data *drv = dev_get_drvdata(pil->dev);
Matt Wagantalld41ce772012-05-10 23:16:41 -070096 int ret;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -070097
Matt Wagantall8c2246d2012-08-12 17:08:04 -070098 ret = pil_lpass_enable_clks(drv);
Matt Wagantalld41ce772012-05-10 23:16:41 -070099 if (ret)
100 return ret;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700101
102 /* Program Image Address */
103 writel_relaxed(((drv->start_addr >> 4) & 0x0FFFFFF0),
104 drv->reg_base + QDSP6SS_RST_EVB);
105
Matt Wagantalld41ce772012-05-10 23:16:41 -0700106 ret = pil_q6v5_reset(pil);
107 if (ret) {
Matt Wagantall8c2246d2012-08-12 17:08:04 -0700108 pil_lpass_disable_clks(drv);
Matt Wagantalld41ce772012-05-10 23:16:41 -0700109 return ret;
110 }
111
112 drv->is_booted = true;
113
114 return 0;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700115}
116
117static struct pil_reset_ops pil_lpass_ops = {
118 .init_image = pil_q6v5_init_image,
119 .proxy_vote = pil_q6v5_make_proxy_votes,
120 .proxy_unvote = pil_q6v5_remove_proxy_votes,
121 .auth_and_reset = pil_lpass_reset,
122 .shutdown = pil_lpass_shutdown,
123};
124
125static int __devinit pil_lpass_driver_probe(struct platform_device *pdev)
126{
127 struct q6v5_data *drv;
128 struct pil_desc *desc;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700129
130 desc = pil_q6v5_init(pdev);
Matt Wagantall55252f12012-05-02 18:02:54 -0700131 if (IS_ERR(desc))
132 return PTR_ERR(desc);
133
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700134 drv = platform_get_drvdata(pdev);
Matt Wagantall55252f12012-05-02 18:02:54 -0700135 if (drv == NULL)
136 return -ENODEV;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700137
138 desc->ops = &pil_lpass_ops;
139 desc->owner = THIS_MODULE;
Matt Wagantall4d89c2e2012-05-25 19:28:34 -0700140 desc->proxy_timeout = PROXY_TIMEOUT_MS;
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700141
Matt Wagantall8c2246d2012-08-12 17:08:04 -0700142 drv->core_clk = devm_clk_get(&pdev->dev, "core_clk");
143 if (IS_ERR(drv->core_clk))
144 return PTR_ERR(drv->core_clk);
145
146 drv->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk");
147 if (IS_ERR(drv->ahb_clk))
148 return PTR_ERR(drv->ahb_clk);
149
150 drv->axi_clk = devm_clk_get(&pdev->dev, "bus_clk");
151 if (IS_ERR(drv->axi_clk))
152 return PTR_ERR(drv->axi_clk);
153
154 drv->reg_clk = devm_clk_get(&pdev->dev, "reg_clk");
155 if (IS_ERR(drv->reg_clk))
156 return PTR_ERR(drv->reg_clk);
Matt Wagantall56865f02012-08-09 15:03:36 -0700157
Matt Wagantallc2bbdc32012-03-21 19:44:50 -0700158 drv->pil = msm_pil_register(desc);
159 if (IS_ERR(drv->pil))
160 return PTR_ERR(drv->pil);
161
162 return 0;
163}
164
165static int __devexit pil_lpass_driver_exit(struct platform_device *pdev)
166{
167 struct q6v5_data *drv = platform_get_drvdata(pdev);
168 msm_pil_unregister(drv->pil);
169 return 0;
170}
171
172static struct of_device_id lpass_match_table[] = {
173 { .compatible = "qcom,pil-q6v5-lpass" },
174 {}
175};
176
177static struct platform_driver pil_lpass_driver = {
178 .probe = pil_lpass_driver_probe,
179 .remove = __devexit_p(pil_lpass_driver_exit),
180 .driver = {
181 .name = "pil-q6v5-lpass",
182 .of_match_table = lpass_match_table,
183 .owner = THIS_MODULE,
184 },
185};
186
187static int __init pil_lpass_init(void)
188{
189 return platform_driver_register(&pil_lpass_driver);
190}
191module_init(pil_lpass_init);
192
193static void __exit pil_lpass_exit(void)
194{
195 platform_driver_unregister(&pil_lpass_driver);
196}
197module_exit(pil_lpass_exit);
198
199MODULE_DESCRIPTION("Support for booting low-power audio subsystems with QDSP6v5 (Hexagon) processors");
200MODULE_LICENSE("GPL v2");