| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 1 | /* | 
| Duy Truong | e833aca | 2013-02-12 13:35:08 -0800 | [diff] [blame^] | 2 | * Copyright (c) 2012, The Linux Foundation. All rights reserved. | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 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 | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 18 | #include <linux/iopoll.h> | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 19 | #include <linux/elf.h> | 
|  | 20 | #include <linux/err.h> | 
|  | 21 | #include <linux/of.h> | 
|  | 22 | #include <linux/clk.h> | 
| Matt Wagantall | d41ce77 | 2012-05-10 23:16:41 -0700 | [diff] [blame] | 23 | #include <mach/clk.h> | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 24 | #include "peripheral-loader.h" | 
|  | 25 | #include "pil-q6v5.h" | 
|  | 26 |  | 
| Matt Wagantall | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 27 | /* QDSP6SS Register Offsets */ | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 28 | #define QDSP6SS_RESET			0x014 | 
|  | 29 | #define QDSP6SS_GFMUX_CTL		0x020 | 
|  | 30 | #define QDSP6SS_PWR_CTL			0x030 | 
| Matt Wagantall | b76f190 | 2012-08-09 14:51:28 -0700 | [diff] [blame] | 31 | #define QDSP6SS_CGC_OVERRIDE		0x034 | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 32 |  | 
| Matt Wagantall | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 33 | /* AXI Halt Register Offsets */ | 
|  | 34 | #define AXI_HALTREQ			0x0 | 
|  | 35 | #define AXI_HALTACK			0x4 | 
|  | 36 | #define AXI_IDLE			0x8 | 
|  | 37 |  | 
|  | 38 | #define HALT_ACK_TIMEOUT_US		100000 | 
|  | 39 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 40 | /* QDSP6SS_RESET */ | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 41 | #define Q6SS_STOP_CORE			BIT(0) | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 42 | #define Q6SS_CORE_ARES			BIT(1) | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 43 | #define Q6SS_BUS_ARES_ENA		BIT(2) | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 44 |  | 
|  | 45 | /* QDSP6SS_GFMUX_CTL */ | 
|  | 46 | #define Q6SS_CLK_ENA			BIT(1) | 
|  | 47 |  | 
|  | 48 | /* QDSP6SS_PWR_CTL */ | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 49 | #define Q6SS_L2DATA_SLP_NRET_N		(BIT(0)|BIT(1)|BIT(2)) | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 50 | #define Q6SS_L2TAG_SLP_NRET_N		BIT(16) | 
|  | 51 | #define Q6SS_ETB_SLP_NRET_N		BIT(17) | 
|  | 52 | #define Q6SS_L2DATA_STBY_N		BIT(18) | 
|  | 53 | #define Q6SS_SLP_RET_N			BIT(19) | 
|  | 54 | #define Q6SS_CLAMP_IO			BIT(20) | 
|  | 55 | #define QDSS_BHS_ON			BIT(21) | 
|  | 56 |  | 
| Matt Wagantall | b76f190 | 2012-08-09 14:51:28 -0700 | [diff] [blame] | 57 | /* QDSP6SS_CGC_OVERRIDE */ | 
|  | 58 | #define Q6SS_CORE_CLK_EN		BIT(0) | 
|  | 59 | #define Q6SS_CORE_RCLK_EN		BIT(1) | 
|  | 60 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 61 | int pil_q6v5_make_proxy_votes(struct pil_desc *pil) | 
|  | 62 | { | 
|  | 63 | int ret; | 
|  | 64 | struct q6v5_data *drv = dev_get_drvdata(pil->dev); | 
|  | 65 |  | 
|  | 66 | ret = clk_prepare_enable(drv->xo); | 
|  | 67 | if (ret) { | 
|  | 68 | dev_err(pil->dev, "Failed to enable XO\n"); | 
|  | 69 | return ret; | 
|  | 70 | } | 
|  | 71 | return 0; | 
|  | 72 | } | 
|  | 73 | EXPORT_SYMBOL(pil_q6v5_make_proxy_votes); | 
|  | 74 |  | 
|  | 75 | void pil_q6v5_remove_proxy_votes(struct pil_desc *pil) | 
|  | 76 | { | 
|  | 77 | struct q6v5_data *drv = dev_get_drvdata(pil->dev); | 
|  | 78 | clk_disable_unprepare(drv->xo); | 
|  | 79 | } | 
|  | 80 | EXPORT_SYMBOL(pil_q6v5_remove_proxy_votes); | 
|  | 81 |  | 
| Matt Wagantall | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 82 | void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base) | 
|  | 83 | { | 
|  | 84 | int ret; | 
|  | 85 | u32 status; | 
|  | 86 |  | 
|  | 87 | /* Assert halt request */ | 
|  | 88 | writel_relaxed(1, halt_base + AXI_HALTREQ); | 
|  | 89 |  | 
|  | 90 | /* Wait for halt */ | 
|  | 91 | ret = readl_poll_timeout(halt_base + AXI_HALTACK, | 
|  | 92 | status, status != 0, 50, HALT_ACK_TIMEOUT_US); | 
|  | 93 | if (ret) | 
|  | 94 | dev_warn(pil->dev, "Port %p halt timeout\n", halt_base); | 
|  | 95 | else if (!readl_relaxed(halt_base + AXI_IDLE)) | 
|  | 96 | dev_warn(pil->dev, "Port %p halt failed\n", halt_base); | 
|  | 97 |  | 
|  | 98 | /* Clear halt request (port will remain halted until reset) */ | 
|  | 99 | writel_relaxed(0, halt_base + AXI_HALTREQ); | 
|  | 100 | } | 
|  | 101 | EXPORT_SYMBOL(pil_q6v5_halt_axi_port); | 
|  | 102 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 103 | int pil_q6v5_init_image(struct pil_desc *pil, const u8 *metadata, | 
|  | 104 | size_t size) | 
|  | 105 | { | 
|  | 106 | const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; | 
|  | 107 | struct q6v5_data *drv = dev_get_drvdata(pil->dev); | 
|  | 108 | drv->start_addr = ehdr->e_entry; | 
|  | 109 | return 0; | 
|  | 110 | } | 
|  | 111 | EXPORT_SYMBOL(pil_q6v5_init_image); | 
|  | 112 |  | 
|  | 113 | void pil_q6v5_shutdown(struct pil_desc *pil) | 
|  | 114 | { | 
|  | 115 | u32 val; | 
|  | 116 | struct q6v5_data *drv = dev_get_drvdata(pil->dev); | 
|  | 117 |  | 
|  | 118 | /* Turn off core clock */ | 
|  | 119 | val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); | 
|  | 120 | val &= ~Q6SS_CLK_ENA; | 
|  | 121 | writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); | 
|  | 122 |  | 
|  | 123 | /* Clamp IO */ | 
|  | 124 | val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 125 | val |= Q6SS_CLAMP_IO; | 
|  | 126 | writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 127 |  | 
|  | 128 | /* Turn off Q6 memories */ | 
|  | 129 | val &= ~(Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | | 
|  | 130 | Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N | | 
|  | 131 | Q6SS_L2DATA_STBY_N); | 
|  | 132 | writel_relaxed(Q6SS_CLAMP_IO, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 133 |  | 
|  | 134 | /* Assert Q6 resets */ | 
|  | 135 | val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 136 | val = (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA); | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 137 | writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); | 
|  | 138 |  | 
|  | 139 | /* Kill power at block headswitch (affects LPASS only) */ | 
|  | 140 | val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 141 | val &= ~QDSS_BHS_ON; | 
|  | 142 | writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 143 | } | 
|  | 144 | EXPORT_SYMBOL(pil_q6v5_shutdown); | 
|  | 145 |  | 
|  | 146 | int pil_q6v5_reset(struct pil_desc *pil) | 
|  | 147 | { | 
|  | 148 | struct q6v5_data *drv = dev_get_drvdata(pil->dev); | 
|  | 149 | u32 val; | 
|  | 150 |  | 
|  | 151 | /* Assert resets, stop core */ | 
|  | 152 | val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 153 | val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE); | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 154 | writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); | 
|  | 155 |  | 
|  | 156 | /* Enable power block headswitch (only affects LPASS) */ | 
|  | 157 | val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 158 | val |= QDSS_BHS_ON; | 
|  | 159 | writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 160 |  | 
|  | 161 | /* Turn on memories */ | 
|  | 162 | val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 163 | val |= Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | | 
|  | 164 | Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N | | 
|  | 165 | Q6SS_L2DATA_STBY_N; | 
|  | 166 | writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 167 |  | 
|  | 168 | /* Remove IO clamp */ | 
|  | 169 | val &= ~Q6SS_CLAMP_IO; | 
|  | 170 | writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); | 
|  | 171 |  | 
|  | 172 | /* Bring core out of reset */ | 
| Matt Wagantall | 11c07e2 | 2012-08-09 16:14:07 -0700 | [diff] [blame] | 173 | val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); | 
|  | 174 | val &= ~Q6SS_CORE_ARES; | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 175 | writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); | 
|  | 176 |  | 
| Matt Wagantall | b76f190 | 2012-08-09 14:51:28 -0700 | [diff] [blame] | 177 | /* Disable clock gating for core and rclk */ | 
|  | 178 | val = readl_relaxed(drv->reg_base + QDSP6SS_CGC_OVERRIDE); | 
|  | 179 | val |= Q6SS_CORE_RCLK_EN | Q6SS_CORE_CLK_EN; | 
|  | 180 | writel_relaxed(val, drv->reg_base + QDSP6SS_CGC_OVERRIDE); | 
|  | 181 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 182 | /* Turn on core clock */ | 
|  | 183 | val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); | 
|  | 184 | val |= Q6SS_CLK_ENA; | 
|  | 185 | writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); | 
|  | 186 |  | 
|  | 187 | /* Start core execution */ | 
|  | 188 | val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); | 
|  | 189 | val &= ~Q6SS_STOP_CORE; | 
|  | 190 | writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); | 
|  | 191 |  | 
|  | 192 | return 0; | 
|  | 193 | } | 
|  | 194 | EXPORT_SYMBOL(pil_q6v5_reset); | 
|  | 195 |  | 
|  | 196 | struct pil_desc __devinit *pil_q6v5_init(struct platform_device *pdev) | 
|  | 197 | { | 
|  | 198 | struct q6v5_data *drv; | 
|  | 199 | struct resource *res; | 
|  | 200 | struct pil_desc *desc; | 
|  | 201 | int ret; | 
|  | 202 |  | 
|  | 203 | drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); | 
|  | 204 | if (!drv) | 
|  | 205 | return ERR_PTR(-ENOMEM); | 
|  | 206 | platform_set_drvdata(pdev, drv); | 
|  | 207 |  | 
|  | 208 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 209 | if (!res) | 
|  | 210 | return ERR_PTR(-EINVAL); | 
|  | 211 | drv->reg_base = devm_ioremap(&pdev->dev, res->start, | 
|  | 212 | resource_size(res)); | 
|  | 213 | if (!drv->reg_base) | 
|  | 214 | return ERR_PTR(-ENOMEM); | 
| Matt Wagantall | b774799 | 2012-05-11 19:37:51 -0700 | [diff] [blame] | 215 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | 
|  | 216 | drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start, | 
|  | 217 | resource_size(res)); | 
|  | 218 | if (!drv->axi_halt_base) | 
|  | 219 | return ERR_PTR(-ENOMEM); | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 220 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 221 | desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL); | 
|  | 222 | if (!desc) | 
|  | 223 | return ERR_PTR(-ENOMEM); | 
|  | 224 |  | 
|  | 225 | ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", | 
|  | 226 | &desc->name); | 
|  | 227 | if (ret) | 
|  | 228 | return ERR_PTR(ret); | 
|  | 229 |  | 
|  | 230 | drv->xo = devm_clk_get(&pdev->dev, "xo"); | 
|  | 231 | if (IS_ERR(drv->xo)) | 
|  | 232 | return ERR_CAST(drv->xo); | 
|  | 233 |  | 
| Matt Wagantall | b3fe899 | 2011-12-07 19:26:55 -0800 | [diff] [blame] | 234 | desc->dev = &pdev->dev; | 
|  | 235 |  | 
|  | 236 | return desc; | 
|  | 237 | } | 
|  | 238 | EXPORT_SYMBOL(pil_q6v5_init); |