Aparna Das | 3b8a708 | 2013-04-02 13:51:13 -0700 | [diff] [blame] | 1 | /* Copyright (c) 2013, The Linux Foundation. All rights reserved. |
| 2 | * |
| 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/kernel.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/init.h> |
| 16 | #include <linux/device.h> |
| 17 | #include <linux/platform_device.h> |
| 18 | #include <linux/io.h> |
| 19 | #include <linux/err.h> |
| 20 | #include <linux/slab.h> |
| 21 | #include <linux/mutex.h> |
| 22 | #include <linux/clk.h> |
| 23 | #include <linux/of_coresight.h> |
| 24 | #include <linux/coresight.h> |
| 25 | #include <linux/of.h> |
| 26 | |
| 27 | #include "coresight-priv.h" |
| 28 | |
| 29 | struct hwevent_mux { |
| 30 | phys_addr_t start; |
| 31 | phys_addr_t end; |
| 32 | }; |
| 33 | |
| 34 | struct hwevent_drvdata { |
| 35 | struct device *dev; |
| 36 | struct coresight_device *csdev; |
| 37 | struct clk *clk; |
| 38 | struct mutex mutex; |
| 39 | int nr_hclk; |
| 40 | struct clk **hclk; |
| 41 | int nr_hmux; |
| 42 | struct hwevent_mux *hmux; |
| 43 | bool enable; |
| 44 | }; |
| 45 | |
| 46 | static int hwevent_enable(struct hwevent_drvdata *drvdata) |
| 47 | { |
| 48 | int ret, i; |
| 49 | |
| 50 | mutex_lock(&drvdata->mutex); |
| 51 | |
| 52 | if (drvdata->enable) |
| 53 | goto out; |
| 54 | |
| 55 | ret = clk_prepare_enable(drvdata->clk); |
| 56 | if (ret) |
| 57 | goto err0; |
| 58 | for (i = 0; i < drvdata->nr_hclk; i++) { |
| 59 | ret = clk_prepare_enable(drvdata->hclk[i]); |
| 60 | if (ret) |
| 61 | goto err1; |
| 62 | } |
| 63 | drvdata->enable = true; |
| 64 | dev_info(drvdata->dev, "Hardware Event driver enabled\n"); |
| 65 | out: |
| 66 | mutex_unlock(&drvdata->mutex); |
| 67 | return 0; |
| 68 | err1: |
| 69 | clk_disable_unprepare(drvdata->clk); |
| 70 | for (i--; i >= 0; i--) |
| 71 | clk_disable_unprepare(drvdata->hclk[i]); |
| 72 | err0: |
| 73 | mutex_unlock(&drvdata->mutex); |
| 74 | return ret; |
| 75 | } |
| 76 | |
| 77 | static void hwevent_disable(struct hwevent_drvdata *drvdata) |
| 78 | { |
| 79 | int i; |
| 80 | |
| 81 | mutex_lock(&drvdata->mutex); |
| 82 | |
| 83 | if (!drvdata->enable) |
| 84 | goto out; |
| 85 | |
| 86 | drvdata->enable = false; |
| 87 | clk_disable_unprepare(drvdata->clk); |
| 88 | for (i = 0; i < drvdata->nr_hclk; i++) |
| 89 | clk_disable_unprepare(drvdata->hclk[i]); |
| 90 | dev_info(drvdata->dev, "Hardware Event driver disabled\n"); |
| 91 | out: |
| 92 | mutex_unlock(&drvdata->mutex); |
| 93 | } |
| 94 | |
| 95 | static ssize_t hwevent_show_enable(struct device *dev, |
| 96 | struct device_attribute *attr, char *buf) |
| 97 | { |
| 98 | struct hwevent_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| 99 | unsigned long val = drvdata->enable; |
| 100 | |
| 101 | return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); |
| 102 | } |
| 103 | |
| 104 | static ssize_t hwevent_store_enable(struct device *dev, |
| 105 | struct device_attribute *attr, |
| 106 | const char *buf, size_t size) |
| 107 | { |
| 108 | struct hwevent_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| 109 | unsigned long val; |
| 110 | int ret = 0; |
| 111 | |
| 112 | if (sscanf(buf, "%lx", &val) != 1) |
| 113 | return -EINVAL; |
| 114 | |
| 115 | if (val) |
| 116 | ret = hwevent_enable(drvdata); |
| 117 | else |
| 118 | hwevent_disable(drvdata); |
| 119 | |
| 120 | if (ret) |
| 121 | return ret; |
| 122 | return size; |
| 123 | } |
| 124 | static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, hwevent_show_enable, |
| 125 | hwevent_store_enable); |
| 126 | |
| 127 | static ssize_t hwevent_store_setreg(struct device *dev, |
| 128 | struct device_attribute *attr, |
| 129 | const char *buf, size_t size) |
| 130 | { |
| 131 | struct hwevent_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| 132 | void *hwereg; |
Pushkar Joshi | c791fb3 | 2013-07-22 15:19:42 -0700 | [diff] [blame] | 133 | unsigned long long addr; |
| 134 | unsigned long val; |
Aparna Das | 3b8a708 | 2013-04-02 13:51:13 -0700 | [diff] [blame] | 135 | int ret, i; |
| 136 | |
Pushkar Joshi | c791fb3 | 2013-07-22 15:19:42 -0700 | [diff] [blame] | 137 | if (sscanf(buf, "%llx %lx", &addr, &val) != 2) |
Aparna Das | 3b8a708 | 2013-04-02 13:51:13 -0700 | [diff] [blame] | 138 | return -EINVAL; |
| 139 | |
| 140 | mutex_lock(&drvdata->mutex); |
| 141 | |
| 142 | if (!drvdata->enable) { |
| 143 | dev_err(dev, "Hardware Event driver not enabled\n"); |
| 144 | ret = -EINVAL; |
| 145 | goto err; |
| 146 | } |
| 147 | |
| 148 | for (i = 0; i < drvdata->nr_hmux; i++) { |
| 149 | if ((addr >= drvdata->hmux[i].start) && |
| 150 | (addr < drvdata->hmux[i].end)) { |
| 151 | hwereg = devm_ioremap(dev, |
| 152 | drvdata->hmux[i].start, |
| 153 | drvdata->hmux[i].end - |
| 154 | drvdata->hmux[i].start); |
| 155 | if (!hwereg) { |
Pushkar Joshi | c791fb3 | 2013-07-22 15:19:42 -0700 | [diff] [blame] | 156 | dev_err(dev, "unable to map address 0x%llx\n", |
Aparna Das | 3b8a708 | 2013-04-02 13:51:13 -0700 | [diff] [blame] | 157 | addr); |
| 158 | ret = -ENOMEM; |
| 159 | goto err; |
| 160 | } |
| 161 | writel_relaxed(val, hwereg + addr - |
| 162 | drvdata->hmux[i].start); |
| 163 | /* Ensure writes to hwevent control registers |
| 164 | are completed before unmapping the address |
| 165 | */ |
| 166 | mb(); |
| 167 | devm_iounmap(dev, hwereg); |
| 168 | break; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | if (i == drvdata->nr_hmux) { |
| 173 | ret = coresight_csr_hwctrl_set(addr, val); |
| 174 | if (ret) { |
| 175 | dev_err(dev, "invalid mux control register address\n"); |
| 176 | ret = -EINVAL; |
| 177 | goto err; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | mutex_unlock(&drvdata->mutex); |
| 182 | return size; |
| 183 | err: |
| 184 | mutex_unlock(&drvdata->mutex); |
| 185 | return ret; |
| 186 | } |
| 187 | static DEVICE_ATTR(setreg, S_IWUSR, NULL, hwevent_store_setreg); |
| 188 | |
| 189 | static struct attribute *hwevent_attrs[] = { |
| 190 | &dev_attr_enable.attr, |
| 191 | &dev_attr_setreg.attr, |
| 192 | NULL, |
| 193 | }; |
| 194 | |
| 195 | static struct attribute_group hwevent_attr_grp = { |
| 196 | .attrs = hwevent_attrs, |
| 197 | }; |
| 198 | |
| 199 | static const struct attribute_group *hwevent_attr_grps[] = { |
| 200 | &hwevent_attr_grp, |
| 201 | NULL, |
| 202 | }; |
| 203 | |
| 204 | static int __devinit hwevent_probe(struct platform_device *pdev) |
| 205 | { |
| 206 | struct device *dev = &pdev->dev; |
| 207 | struct hwevent_drvdata *drvdata; |
| 208 | struct coresight_desc *desc; |
| 209 | struct coresight_platform_data *pdata; |
| 210 | struct resource *res; |
| 211 | int ret, i; |
| 212 | const char *hmux_name, *hclk_name; |
| 213 | |
Pratik Patel | 4930640 | 2013-06-14 01:02:08 -0700 | [diff] [blame] | 214 | if (coresight_fuse_access_disabled()) |
| 215 | return -EPERM; |
| 216 | |
Aparna Das | 3b8a708 | 2013-04-02 13:51:13 -0700 | [diff] [blame] | 217 | if (pdev->dev.of_node) { |
| 218 | pdata = of_get_coresight_platform_data(dev, pdev->dev.of_node); |
| 219 | if (IS_ERR(pdata)) |
| 220 | return PTR_ERR(pdata); |
| 221 | pdev->dev.platform_data = pdata; |
| 222 | } |
| 223 | |
| 224 | drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); |
| 225 | if (!drvdata) |
| 226 | return -ENOMEM; |
| 227 | drvdata->dev = &pdev->dev; |
| 228 | platform_set_drvdata(pdev, drvdata); |
| 229 | |
| 230 | if (pdev->dev.of_node) |
| 231 | drvdata->nr_hmux = of_property_count_strings(pdev->dev.of_node, |
| 232 | "reg-names"); |
| 233 | |
| 234 | if (drvdata->nr_hmux > 0) { |
| 235 | drvdata->hmux = devm_kzalloc(dev, drvdata->nr_hmux * |
| 236 | sizeof(*drvdata->hmux), |
| 237 | GFP_KERNEL); |
| 238 | if (!drvdata->hmux) |
| 239 | return -ENOMEM; |
| 240 | for (i = 0; i < drvdata->nr_hmux; i++) { |
| 241 | ret = of_property_read_string_index(pdev->dev.of_node, |
| 242 | "reg-names", i, |
| 243 | &hmux_name); |
| 244 | if (ret) |
| 245 | return ret; |
| 246 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| 247 | hmux_name); |
| 248 | if (!res) |
| 249 | return -ENODEV; |
| 250 | drvdata->hmux[i].start = res->start; |
| 251 | drvdata->hmux[i].end = res->end; |
| 252 | } |
| 253 | } else if (drvdata->nr_hmux < 0) { |
| 254 | return drvdata->nr_hmux; |
| 255 | } else { |
| 256 | /* return error if reg-names in dt node is empty string */ |
| 257 | return -ENODEV; |
| 258 | } |
| 259 | |
| 260 | mutex_init(&drvdata->mutex); |
| 261 | |
| 262 | drvdata->clk = devm_clk_get(dev, "core_clk"); |
| 263 | if (IS_ERR(drvdata->clk)) |
| 264 | return PTR_ERR(drvdata->clk); |
| 265 | |
| 266 | ret = clk_set_rate(drvdata->clk, CORESIGHT_CLK_RATE_TRACE); |
| 267 | if (ret) |
| 268 | return ret; |
| 269 | |
| 270 | if (pdev->dev.of_node) |
| 271 | drvdata->nr_hclk = of_property_count_strings(pdev->dev.of_node, |
| 272 | "qcom,hwevent-clks"); |
| 273 | if (drvdata->nr_hclk > 0) { |
| 274 | drvdata->hclk = devm_kzalloc(dev, drvdata->nr_hclk * |
| 275 | sizeof(*drvdata->hclk), |
| 276 | GFP_KERNEL); |
| 277 | if (!drvdata->hclk) |
| 278 | return -ENOMEM; |
| 279 | for (i = 0; i < drvdata->nr_hclk; i++) { |
| 280 | ret = of_property_read_string_index(pdev->dev.of_node, |
| 281 | "qcom,hwevent-clks", |
| 282 | i, &hclk_name); |
| 283 | if (ret) |
| 284 | return ret; |
| 285 | drvdata->hclk[i] = devm_clk_get(dev, hclk_name); |
| 286 | if (IS_ERR(drvdata->hclk[i])) |
| 287 | return PTR_ERR(drvdata->hclk[i]); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); |
| 292 | if (!desc) |
| 293 | return -ENOMEM; |
| 294 | |
| 295 | desc->type = CORESIGHT_DEV_TYPE_NONE; |
| 296 | desc->pdata = pdev->dev.platform_data; |
| 297 | desc->dev = &pdev->dev; |
| 298 | desc->groups = hwevent_attr_grps; |
| 299 | desc->owner = THIS_MODULE; |
| 300 | drvdata->csdev = coresight_register(desc); |
| 301 | if (IS_ERR(drvdata->csdev)) |
| 302 | return PTR_ERR(drvdata->csdev); |
| 303 | |
| 304 | dev_info(dev, "Hardware Event driver initialized\n"); |
| 305 | return 0; |
| 306 | } |
| 307 | |
| 308 | static int __devexit hwevent_remove(struct platform_device *pdev) |
| 309 | { |
| 310 | struct hwevent_drvdata *drvdata = platform_get_drvdata(pdev); |
| 311 | |
| 312 | coresight_unregister(drvdata->csdev); |
| 313 | return 0; |
| 314 | } |
| 315 | |
| 316 | static struct of_device_id hwevent_match[] = { |
| 317 | {.compatible = "qcom,coresight-hwevent"}, |
| 318 | {} |
| 319 | }; |
| 320 | |
| 321 | static struct platform_driver hwevent_driver = { |
| 322 | .probe = hwevent_probe, |
| 323 | .remove = __devexit_p(hwevent_remove), |
| 324 | .driver = { |
| 325 | .name = "coresight-hwevent", |
| 326 | .owner = THIS_MODULE, |
| 327 | .of_match_table = hwevent_match, |
| 328 | }, |
| 329 | }; |
| 330 | |
| 331 | static int __init hwevent_init(void) |
| 332 | { |
| 333 | return platform_driver_register(&hwevent_driver); |
| 334 | } |
| 335 | module_init(hwevent_init); |
| 336 | |
| 337 | static void __exit hwevent_exit(void) |
| 338 | { |
| 339 | platform_driver_unregister(&hwevent_driver); |
| 340 | } |
| 341 | module_exit(hwevent_exit); |
| 342 | |
| 343 | MODULE_LICENSE("GPL v2"); |
| 344 | MODULE_DESCRIPTION("CoreSight Hardware Event driver"); |