| Duy Truong | e833aca | 2013-02-12 13:35:08 -0800 | [diff] [blame] | 1 | /* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 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 | #define pr_fmt(fmt) "%s: " fmt, __func__ | 
|  | 14 |  | 
|  | 15 | #include <linux/kernel.h> | 
|  | 16 | #include <linux/platform_device.h> | 
|  | 17 | #include <linux/err.h> | 
|  | 18 | #include <linux/regulator/driver.h> | 
|  | 19 | #include <linux/regulator/machine.h> | 
|  | 20 | #include <linux/clk.h> | 
| Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame] | 21 | #include <linux/module.h> | 
| Taniya Das | 78b72d6 | 2011-12-02 15:54:21 +0530 | [diff] [blame] | 22 | #include <mach/socinfo.h> | 
| Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame] | 23 | #include <mach/proc_comm.h> | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 24 | #include "footswitch.h" | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 25 |  | 
|  | 26 | /* PCOM power rail IDs */ | 
|  | 27 | #define PCOM_FS_GRP		8 | 
|  | 28 | #define PCOM_FS_GRP_2D		58 | 
|  | 29 | #define PCOM_FS_MDP		14 | 
|  | 30 | #define PCOM_FS_MFC		68 | 
|  | 31 | #define PCOM_FS_ROTATOR		90 | 
|  | 32 | #define PCOM_FS_VFE		41 | 
|  | 33 | #define PCOM_FS_VPE		76 | 
|  | 34 |  | 
|  | 35 | #define PCOM_RAIL_MODE_AUTO	0 | 
|  | 36 | #define PCOM_RAIL_MODE_MANUAL	1 | 
|  | 37 |  | 
|  | 38 | /** | 
|  | 39 | * struct footswitch - Per-footswitch data and state | 
|  | 40 | * @rdev: Regulator framework device | 
|  | 41 | * @desc: Regulator descriptor | 
|  | 42 | * @init_data: Regulator platform data | 
|  | 43 | * @pcom_id: Proc-comm ID of the footswitch | 
|  | 44 | * @is_enabled: Flag set when footswitch is enabled | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 45 | * @has_ahb_clk: Flag set if footswitched core has an ahb_clk | 
|  | 46 | * @has_src_clk: Flag set if footswitched core has a src_clk | 
|  | 47 | * @src_clk: Controls the core clock's rate | 
|  | 48 | * @core_clk: Clocks the core | 
|  | 49 | * @ahb_clk: Clocks the core's register interface | 
|  | 50 | * @src_clk_init_rate: Rate to use for src_clk if it has not been set yet | 
|  | 51 | * @is_rate_set: Flag set if core_clk's rate has been set | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 52 | */ | 
|  | 53 | struct footswitch { | 
|  | 54 | struct regulator_dev			*rdev; | 
|  | 55 | struct regulator_desc			desc; | 
|  | 56 | struct regulator_init_data		init_data; | 
|  | 57 | unsigned				pcom_id; | 
|  | 58 | bool					is_enabled; | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 59 | struct clk				*src_clk; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 60 | struct clk				*core_clk; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 61 | struct clk				*ahb_clk; | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 62 | const bool				has_ahb_clk; | 
|  | 63 | const bool				has_src_clk; | 
|  | 64 | const int				src_clk_init_rate; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 65 | bool					is_rate_set; | 
|  | 66 | }; | 
|  | 67 |  | 
|  | 68 | static inline int set_rail_mode(int pcom_id, int mode) | 
|  | 69 | { | 
|  | 70 | int  rc; | 
|  | 71 |  | 
|  | 72 | rc = msm_proc_comm(PCOM_CLKCTL_RPC_RAIL_CONTROL, &pcom_id, &mode); | 
|  | 73 | if (!rc && pcom_id) | 
|  | 74 | rc = -EINVAL; | 
|  | 75 |  | 
|  | 76 | return rc; | 
|  | 77 | } | 
|  | 78 |  | 
|  | 79 | static inline int set_rail_state(int pcom_id, int state) | 
|  | 80 | { | 
|  | 81 | int  rc; | 
|  | 82 |  | 
|  | 83 | rc = msm_proc_comm(state, &pcom_id, NULL); | 
|  | 84 | if (!rc && pcom_id) | 
|  | 85 | rc = -EINVAL; | 
|  | 86 |  | 
|  | 87 | return rc; | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | static int enable_clocks(struct footswitch *fs) | 
|  | 91 | { | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 92 | fs->is_rate_set = !!(clk_get_rate(fs->src_clk)); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 93 | if (!fs->is_rate_set) | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 94 | clk_set_rate(fs->src_clk, fs->src_clk_init_rate); | 
| Matt Wagantall | 1a7ee89 | 2012-01-17 18:56:28 -0800 | [diff] [blame] | 95 | clk_prepare_enable(fs->core_clk); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 96 |  | 
|  | 97 | if (fs->ahb_clk) | 
| Matt Wagantall | 1a7ee89 | 2012-01-17 18:56:28 -0800 | [diff] [blame] | 98 | clk_prepare_enable(fs->ahb_clk); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 99 |  | 
|  | 100 | return 0; | 
|  | 101 | } | 
|  | 102 |  | 
|  | 103 | static void disable_clocks(struct footswitch *fs) | 
|  | 104 | { | 
|  | 105 | if (fs->ahb_clk) | 
| Matt Wagantall | 1a7ee89 | 2012-01-17 18:56:28 -0800 | [diff] [blame] | 106 | clk_disable_unprepare(fs->ahb_clk); | 
|  | 107 | clk_disable_unprepare(fs->core_clk); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 108 | } | 
|  | 109 |  | 
|  | 110 | static int footswitch_is_enabled(struct regulator_dev *rdev) | 
|  | 111 | { | 
|  | 112 | struct footswitch *fs = rdev_get_drvdata(rdev); | 
|  | 113 |  | 
|  | 114 | return fs->is_enabled; | 
|  | 115 | } | 
|  | 116 |  | 
|  | 117 | static int footswitch_enable(struct regulator_dev *rdev) | 
|  | 118 | { | 
|  | 119 | struct footswitch *fs = rdev_get_drvdata(rdev); | 
|  | 120 | int rc; | 
|  | 121 |  | 
|  | 122 | rc = enable_clocks(fs); | 
|  | 123 | if (rc) | 
|  | 124 | return rc; | 
|  | 125 |  | 
|  | 126 | rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE); | 
|  | 127 | if (!rc) | 
|  | 128 | fs->is_enabled = true; | 
|  | 129 |  | 
|  | 130 | disable_clocks(fs); | 
|  | 131 |  | 
|  | 132 | return rc; | 
|  | 133 | } | 
|  | 134 |  | 
|  | 135 | static int footswitch_disable(struct regulator_dev *rdev) | 
|  | 136 | { | 
|  | 137 | struct footswitch *fs = rdev_get_drvdata(rdev); | 
|  | 138 | int rc; | 
|  | 139 |  | 
|  | 140 | rc = enable_clocks(fs); | 
|  | 141 | if (rc) | 
|  | 142 | return rc; | 
|  | 143 |  | 
|  | 144 | rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_DISABLE); | 
|  | 145 | if (!rc) | 
|  | 146 | fs->is_enabled = false; | 
|  | 147 |  | 
|  | 148 | disable_clocks(fs); | 
|  | 149 |  | 
|  | 150 | return rc; | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | static struct regulator_ops footswitch_ops = { | 
|  | 154 | .is_enabled = footswitch_is_enabled, | 
|  | 155 | .enable = footswitch_enable, | 
|  | 156 | .disable = footswitch_disable, | 
|  | 157 | }; | 
|  | 158 |  | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 159 | #define FOOTSWITCH(_id, _pcom_id, _name, _src_clk, _rate, _ahb_clk) \ | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 160 | [_id] = { \ | 
|  | 161 | .desc = { \ | 
|  | 162 | .id = _id, \ | 
|  | 163 | .name = _name, \ | 
|  | 164 | .ops = &footswitch_ops, \ | 
|  | 165 | .type = REGULATOR_VOLTAGE, \ | 
|  | 166 | .owner = THIS_MODULE, \ | 
|  | 167 | }, \ | 
|  | 168 | .pcom_id = _pcom_id, \ | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 169 | .has_src_clk = _src_clk, \ | 
|  | 170 | .src_clk_init_rate = _rate, \ | 
|  | 171 | .has_ahb_clk = _ahb_clk, \ | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 172 | } | 
|  | 173 | static struct footswitch footswitches[] = { | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 174 | FOOTSWITCH(FS_GFX3D,  PCOM_FS_GRP, | 
|  | 175 | "fs_gfx3d",   true, 24576000, true), | 
|  | 176 | FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D, | 
|  | 177 | "fs_gfx2d0", false, 24576000, true), | 
|  | 178 | FOOTSWITCH(FS_MDP,    PCOM_FS_MDP, | 
|  | 179 | "fs_mdp",    false, 24576000, true), | 
|  | 180 | FOOTSWITCH(FS_MFC,    PCOM_FS_MFC, | 
|  | 181 | "fs_mfc",    false, 24576000, true), | 
|  | 182 | FOOTSWITCH(FS_ROT,    PCOM_FS_ROTATOR, | 
|  | 183 | "fs_rot",    false,        0, true), | 
|  | 184 | FOOTSWITCH(FS_VFE,    PCOM_FS_VFE, | 
|  | 185 | "fs_vfe",    false, 24576000, true), | 
|  | 186 | FOOTSWITCH(FS_VPE,    PCOM_FS_VPE, | 
|  | 187 | "fs_vpe",    false, 24576000, false), | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 188 | }; | 
|  | 189 |  | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 190 | static int get_clocks(struct device *dev, struct footswitch *fs) | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 191 | { | 
|  | 192 | int rc; | 
|  | 193 |  | 
|  | 194 | /* | 
|  | 195 | * Some SoCs may not have a separate rate-settable clock. | 
|  | 196 | * If one can't be found, try to use the core clock for | 
|  | 197 | * rate-setting instead. | 
|  | 198 | */ | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 199 | if (fs->has_src_clk) { | 
|  | 200 | fs->src_clk = clk_get(dev, "src_clk"); | 
|  | 201 | if (IS_ERR(fs->src_clk)) | 
|  | 202 | fs->src_clk = clk_get(dev, "core_clk"); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 203 | } else { | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 204 | fs->src_clk = clk_get(dev, "core_clk"); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 205 | } | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 206 | if (IS_ERR(fs->src_clk)) { | 
| Matt Wagantall | 27fa282 | 2012-02-22 18:43:27 -0800 | [diff] [blame] | 207 | pr_err("%s clk_get(src_clk) failed\n", fs->desc.name); | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 208 | rc = PTR_ERR(fs->src_clk); | 
|  | 209 | goto err_src_clk; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 210 | } | 
|  | 211 |  | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 212 | fs->core_clk = clk_get(dev, "core_clk"); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 213 | if (IS_ERR(fs->core_clk)) { | 
| Matt Wagantall | 27fa282 | 2012-02-22 18:43:27 -0800 | [diff] [blame] | 214 | pr_err("%s clk_get(core_clk) failed\n", fs->desc.name); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 215 | rc = PTR_ERR(fs->core_clk); | 
|  | 216 | goto err_core_clk; | 
|  | 217 | } | 
|  | 218 |  | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 219 | if (fs->has_ahb_clk) { | 
|  | 220 | fs->ahb_clk = clk_get(dev, "iface_clk"); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 221 | if (IS_ERR(fs->ahb_clk)) { | 
| Matt Wagantall | 27fa282 | 2012-02-22 18:43:27 -0800 | [diff] [blame] | 222 | pr_err("%s clk_get(iface_clk) failed\n", fs->desc.name); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 223 | rc = PTR_ERR(fs->ahb_clk); | 
|  | 224 | goto err_ahb_clk; | 
|  | 225 | } | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | return 0; | 
|  | 229 |  | 
|  | 230 | err_ahb_clk: | 
|  | 231 | clk_put(fs->core_clk); | 
|  | 232 | err_core_clk: | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 233 | clk_put(fs->src_clk); | 
|  | 234 | err_src_clk: | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 235 | return rc; | 
|  | 236 | } | 
|  | 237 |  | 
|  | 238 | static void put_clocks(struct footswitch *fs) | 
|  | 239 | { | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 240 | clk_put(fs->src_clk); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 241 | clk_put(fs->core_clk); | 
|  | 242 | clk_put(fs->ahb_clk); | 
|  | 243 | } | 
|  | 244 |  | 
|  | 245 | static int footswitch_probe(struct platform_device *pdev) | 
|  | 246 | { | 
|  | 247 | struct footswitch *fs; | 
|  | 248 | struct regulator_init_data *init_data; | 
|  | 249 | int rc; | 
|  | 250 |  | 
|  | 251 | if (pdev == NULL) | 
|  | 252 | return -EINVAL; | 
|  | 253 |  | 
|  | 254 | if (pdev->id >= MAX_FS) | 
|  | 255 | return -ENODEV; | 
|  | 256 |  | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 257 | init_data = pdev->dev.platform_data; | 
| Matt Wagantall | c123862 | 2012-06-25 12:12:46 -0700 | [diff] [blame] | 258 | fs = &footswitches[pdev->id]; | 
|  | 259 |  | 
|  | 260 | /* | 
|  | 261 | * Enable footswitch in manual mode (ie. not controlled along | 
|  | 262 | * with pcom clocks). | 
|  | 263 | */ | 
|  | 264 | rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE); | 
|  | 265 | if (rc) | 
|  | 266 | return rc; | 
|  | 267 | rc = set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_MANUAL); | 
|  | 268 | if (rc) | 
|  | 269 | return rc; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 270 |  | 
| Matt Wagantall | 4972271 | 2011-08-17 18:50:53 -0700 | [diff] [blame] | 271 | rc = get_clocks(&pdev->dev, fs); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 272 | if (rc) | 
|  | 273 | return rc; | 
|  | 274 |  | 
| Rajendra Nayak | 11eafc6 | 2011-11-18 16:47:19 +0530 | [diff] [blame] | 275 | fs->rdev = regulator_register(&fs->desc, &pdev->dev, | 
|  | 276 | init_data, fs, NULL); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 277 | if (IS_ERR(fs->rdev)) { | 
|  | 278 | pr_err("regulator_register(%s) failed\n", fs->desc.name); | 
|  | 279 | rc = PTR_ERR(fs->rdev); | 
|  | 280 | goto err_register; | 
|  | 281 | } | 
|  | 282 |  | 
|  | 283 | return 0; | 
|  | 284 |  | 
|  | 285 | err_register: | 
|  | 286 | put_clocks(fs); | 
|  | 287 |  | 
|  | 288 | return rc; | 
|  | 289 | } | 
|  | 290 |  | 
|  | 291 | static int __devexit footswitch_remove(struct platform_device *pdev) | 
|  | 292 | { | 
|  | 293 | struct footswitch *fs = &footswitches[pdev->id]; | 
|  | 294 |  | 
|  | 295 | regulator_unregister(fs->rdev); | 
|  | 296 | set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_AUTO); | 
|  | 297 | put_clocks(fs); | 
|  | 298 |  | 
|  | 299 | return 0; | 
|  | 300 | } | 
|  | 301 |  | 
|  | 302 | static struct platform_driver footswitch_driver = { | 
|  | 303 | .probe		= footswitch_probe, | 
|  | 304 | .remove		= __devexit_p(footswitch_remove), | 
|  | 305 | .driver		= { | 
|  | 306 | .name		= "footswitch-pcom", | 
|  | 307 | .owner		= THIS_MODULE, | 
|  | 308 | }, | 
|  | 309 | }; | 
|  | 310 |  | 
|  | 311 | static int __init footswitch_init(void) | 
|  | 312 | { | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 313 | return platform_driver_register(&footswitch_driver); | 
|  | 314 | } | 
|  | 315 | subsys_initcall(footswitch_init); | 
|  | 316 |  | 
|  | 317 | static void __exit footswitch_exit(void) | 
|  | 318 | { | 
|  | 319 | platform_driver_unregister(&footswitch_driver); | 
|  | 320 | } | 
|  | 321 | module_exit(footswitch_exit); | 
|  | 322 |  | 
| Matt Wagantall | df9d32a | 2011-09-09 13:50:06 -0700 | [diff] [blame] | 323 | MODULE_LICENSE("GPL v2"); | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 324 | MODULE_DESCRIPTION("proc_comm rail footswitch"); | 
|  | 325 | MODULE_ALIAS("platform:footswitch-pcom"); |