blob: 2176b2607a0ac5d37068eba25ad7736ffd005fea [file] [log] [blame]
Matt Wagantalle6e00d52012-03-08 17:39:07 -08001/*
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>
18#include <linux/iopoll.h>
19#include <linux/ioport.h>
20#include <linux/elf.h>
21#include <linux/delay.h>
22#include <linux/sched.h>
23#include <linux/clk.h>
24#include <linux/err.h>
25#include <linux/of.h>
26
27#include "peripheral-loader.h"
28
29#define RMB_MBA_COMMAND 0x08
30#define RMB_MBA_STATUS 0x0C
31#define RMB_PMI_META_DATA 0x10
32#define RMB_PMI_CODE_START 0x14
33#define RMB_PMI_CODE_LENGTH 0x18
34
35#define CMD_META_DATA_READY 0x1
36#define CMD_LOAD_READY 0x2
37
38#define STATUS_META_DATA_AUTH_SUCCESS 0x3
39#define STATUS_AUTH_COMPLETE 0x4
Matt Wagantalle6e00d52012-03-08 17:39:07 -080040
41#define AUTH_TIMEOUT_US 10000000
42#define PROXY_TIMEOUT_MS 10000
43#define POLL_INTERVAL_US 50
44
45struct mba_data {
46 void __iomem *reg_base;
47 void __iomem *metadata_base;
48 unsigned long metadata_phys;
49 struct pil_device *pil;
50 struct clk *xo;
51 u32 img_length;
52};
53
54static int pil_mba_make_proxy_votes(struct pil_desc *pil)
55{
56 int ret;
57 struct mba_data *drv = dev_get_drvdata(pil->dev);
58
59 ret = clk_prepare_enable(drv->xo);
60 if (ret) {
61 dev_err(pil->dev, "Failed to enable XO\n");
62 return ret;
63 }
64 return 0;
65}
66
67static void pil_mba_remove_proxy_votes(struct pil_desc *pil)
68{
69 struct mba_data *drv = dev_get_drvdata(pil->dev);
70 clk_disable_unprepare(drv->xo);
71}
72
73static int pil_mba_init_image(struct pil_desc *pil,
74 const u8 *metadata, size_t size)
75{
76 struct mba_data *drv = dev_get_drvdata(pil->dev);
Matt Wagantall19887262012-08-03 16:37:34 -070077 s32 status;
Matt Wagantalle6e00d52012-03-08 17:39:07 -080078 int ret;
79
80 /* Copy metadata to assigned shared buffer location */
81 memcpy(drv->metadata_base, metadata, size);
82
83 /* Initialize length counter to 0 */
84 writel_relaxed(0, drv->reg_base + RMB_PMI_CODE_LENGTH);
85 drv->img_length = 0;
86
87 /* Pass address of meta-data to the MBA and perform authentication */
88 writel_relaxed(drv->metadata_phys, drv->reg_base + RMB_PMI_META_DATA);
89 writel_relaxed(CMD_META_DATA_READY, drv->reg_base + RMB_MBA_COMMAND);
90 ret = readl_poll_timeout(drv->reg_base + RMB_MBA_STATUS, status,
Matt Wagantall19887262012-08-03 16:37:34 -070091 status == STATUS_META_DATA_AUTH_SUCCESS || status < 0,
Matt Wagantalle6e00d52012-03-08 17:39:07 -080092 POLL_INTERVAL_US, AUTH_TIMEOUT_US);
Matt Wagantall19887262012-08-03 16:37:34 -070093 if (ret) {
Matt Wagantalle6e00d52012-03-08 17:39:07 -080094 dev_err(pil->dev, "MBA authentication timed out\n");
Matt Wagantall19887262012-08-03 16:37:34 -070095 } else if (status < 0) {
96 dev_err(pil->dev, "MBA returned error %d\n", status);
97 ret = -EINVAL;
98 }
Matt Wagantalle6e00d52012-03-08 17:39:07 -080099
100 return ret;
101}
102
103static int pil_mba_verify_blob(struct pil_desc *pil, u32 phy_addr,
104 size_t size)
105{
106 struct mba_data *drv = dev_get_drvdata(pil->dev);
Matt Wagantall19887262012-08-03 16:37:34 -0700107 s32 status;
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800108
109 /* Begin image authentication */
110 if (drv->img_length == 0) {
111 writel_relaxed(phy_addr, drv->reg_base + RMB_PMI_CODE_START);
112 writel_relaxed(CMD_LOAD_READY, drv->reg_base + RMB_MBA_COMMAND);
113 }
114 /* Increment length counter */
115 drv->img_length += size;
116 writel_relaxed(drv->img_length, drv->reg_base + RMB_PMI_CODE_LENGTH);
117
Matt Wagantall19887262012-08-03 16:37:34 -0700118 status = readl_relaxed(drv->reg_base + RMB_MBA_STATUS);
119 if (status < 0) {
120 dev_err(pil->dev, "MBA returned error %d\n", status);
121 return -EINVAL;
122 }
123
124 return 0;
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800125}
126
127static int pil_mba_auth(struct pil_desc *pil)
128{
129 struct mba_data *drv = dev_get_drvdata(pil->dev);
130 int ret;
Matt Wagantall19887262012-08-03 16:37:34 -0700131 s32 status;
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800132
133 /* Wait for all segments to be authenticated or an error to occur */
134 ret = readl_poll_timeout(drv->reg_base + RMB_MBA_STATUS, status,
Matt Wagantall19887262012-08-03 16:37:34 -0700135 status == STATUS_AUTH_COMPLETE || status < 0,
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800136 50, AUTH_TIMEOUT_US);
Matt Wagantall19887262012-08-03 16:37:34 -0700137 if (ret) {
138 dev_err(pil->dev, "MBA authentication timed out\n");
139 } else if (status < 0) {
140 dev_err(pil->dev, "MBA returned error %d\n", status);
141 ret = -EINVAL;
142 }
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800143
Matt Wagantall19887262012-08-03 16:37:34 -0700144 return ret;
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800145}
146
147static int pil_mba_shutdown(struct pil_desc *pil)
148{
149 return 0;
150}
151
152static struct pil_reset_ops pil_mba_ops = {
153 .init_image = pil_mba_init_image,
154 .proxy_vote = pil_mba_make_proxy_votes,
155 .proxy_unvote = pil_mba_remove_proxy_votes,
156 .verify_blob = pil_mba_verify_blob,
157 .auth_and_reset = pil_mba_auth,
158 .shutdown = pil_mba_shutdown,
159};
160
161static int __devinit pil_mba_driver_probe(struct platform_device *pdev)
162{
163 struct mba_data *drv;
164 struct resource *res;
165 struct pil_desc *desc;
166 int ret;
167
168 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
169 if (!drv)
170 return -ENOMEM;
171 platform_set_drvdata(pdev, drv);
172
173 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
174 if (!res)
175 return -EINVAL;
176 drv->reg_base = devm_ioremap(&pdev->dev, res->start,
177 resource_size(res));
178 if (!drv->reg_base)
179 return -ENOMEM;
180
181 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
182 if (res) {
183 drv->metadata_base = devm_ioremap(&pdev->dev, res->start,
184 resource_size(res));
185 if (!drv->metadata_base)
186 return -ENOMEM;
187 drv->metadata_phys = res->start;
188 }
189
190 desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
191 if (!drv)
192 return -ENOMEM;
193
194 ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name",
195 &desc->name);
196 if (ret)
197 return ret;
198
199 of_property_read_string(pdev->dev.of_node, "qcom,depends-on",
200 &desc->depends_on);
201
202 drv->xo = devm_clk_get(&pdev->dev, "xo");
203 if (IS_ERR(drv->xo))
204 return PTR_ERR(drv->xo);
205
206 desc->dev = &pdev->dev;
207 desc->ops = &pil_mba_ops;
208 desc->owner = THIS_MODULE;
209 desc->proxy_timeout = PROXY_TIMEOUT_MS;
210
211 drv->pil = msm_pil_register(desc);
212 if (IS_ERR(drv->pil))
213 return PTR_ERR(drv->pil);
214
215 return 0;
216}
217
218static int __devexit pil_mba_driver_exit(struct platform_device *pdev)
219{
Stephen Boydfb71ba62012-07-02 15:24:42 -0700220 struct mba_data *drv = platform_get_drvdata(pdev);
221 msm_pil_unregister(drv->pil);
Matt Wagantalle6e00d52012-03-08 17:39:07 -0800222 return 0;
223}
224
225static struct of_device_id mba_match_table[] = {
226 { .compatible = "qcom,pil-mba" },
227 {}
228};
229
230struct platform_driver pil_mba_driver = {
231 .probe = pil_mba_driver_probe,
232 .remove = __devexit_p(pil_mba_driver_exit),
233 .driver = {
234 .name = "pil-mba",
235 .of_match_table = mba_match_table,
236 .owner = THIS_MODULE,
237 },
238};
239
240static int __init pil_mba_init(void)
241{
242 return platform_driver_register(&pil_mba_driver);
243}
244module_init(pil_mba_init);
245
246static void __exit pil_mba_exit(void)
247{
248 platform_driver_unregister(&pil_mba_driver);
249}
250module_exit(pil_mba_exit);
251
252MODULE_DESCRIPTION("Support for modem boot using the Modem Boot Authenticator");
253MODULE_LICENSE("GPL v2");