| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [diff] [blame] | 1 | /* | 
| Duy Truong | e833aca | 2013-02-12 13:35:08 -0800 | [diff] [blame] | 2 | * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. | 
| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [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 | #define pr_fmt(fmt) "%s: " fmt, __func__ | 
|  | 15 |  | 
|  | 16 | #include <linux/module.h> | 
|  | 17 | #include <linux/slab.h> | 
|  | 18 | #include <linux/err.h> | 
|  | 19 | #include <linux/kernel.h> | 
|  | 20 | #include <linux/init.h> | 
|  | 21 | #include <linux/platform_device.h> | 
|  | 22 | #include <linux/regulator/driver.h> | 
|  | 23 | #include <linux/regulator/machine.h> | 
| Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame] | 24 | #include <mach/proc_comm.h> | 
| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [diff] [blame] | 25 | #include "proccomm-regulator.h" | 
|  | 26 |  | 
|  | 27 | #define MV_TO_UV(mv) ((mv)*1000) | 
|  | 28 | #define UV_TO_MV(uv) (((uv)+999)/1000) | 
|  | 29 |  | 
|  | 30 | /* | 
|  | 31 | * Wrappers for the msm_proc_comm() calls. | 
|  | 32 | * Does basic impedance matching between what the proccomm interface | 
|  | 33 | * expects and how the driver sees the world. | 
|  | 34 | */ | 
|  | 35 |  | 
|  | 36 | /* Converts a proccomm error to an errno value. */ | 
|  | 37 | static int _pcom_err_to_linux_errno(unsigned error) | 
|  | 38 | { | 
|  | 39 | if (!error)		/* 0 == no error */ | 
|  | 40 | return 0; | 
|  | 41 | else if (error & 0x1F)  /* bits 0..4 => parameter 1..5 out of range */ | 
|  | 42 | return -EDOM; | 
|  | 43 | else if (error & 0x100) /* bit 8 => feature not supported */ | 
|  | 44 | return -ENOSYS; | 
|  | 45 | else			/* anything else non-zero: unknown error */ | 
|  | 46 | return -EINVAL; | 
|  | 47 | } | 
|  | 48 |  | 
|  | 49 | /* vreg_switch: (vreg ID, on/off) => (return code, <null>) */ | 
|  | 50 | static int _vreg_switch(int vreg_id, bool enable) | 
|  | 51 | { | 
|  | 52 | unsigned _id		= (unsigned)vreg_id; | 
|  | 53 | unsigned _enable	= !!enable; | 
|  | 54 |  | 
|  | 55 | return msm_proc_comm(PCOM_VREG_SWITCH, &_id, &_enable); | 
|  | 56 | } | 
|  | 57 |  | 
|  | 58 | /* vreg_set_level: (vreg ID, mV) => (return code, <null>) */ | 
|  | 59 | static int _vreg_set_level(int vreg_id, int level_mV) | 
|  | 60 | { | 
|  | 61 | unsigned _id		= (unsigned)vreg_id; | 
|  | 62 | unsigned _level		= (unsigned)level_mV; | 
|  | 63 | int	 rc; | 
|  | 64 |  | 
|  | 65 | rc = msm_proc_comm(PCOM_VREG_SET_LEVEL, &_id, &_level); | 
|  | 66 |  | 
|  | 67 | if (rc) | 
|  | 68 | return rc; | 
|  | 69 |  | 
|  | 70 | return _pcom_err_to_linux_errno(_id); | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | /* vreg_pull_down: (pull down, vreg ID) => (<null>, <null>) */ | 
|  | 74 | /* Returns error code from msm_proc_comm. */ | 
|  | 75 | static int _vreg_pull_down(int vreg_id, bool pull_down) | 
|  | 76 | { | 
|  | 77 | unsigned _id		= (unsigned)vreg_id; | 
|  | 78 | unsigned _enable	= !!pull_down; | 
|  | 79 |  | 
|  | 80 | return msm_proc_comm(PCOM_VREG_PULLDOWN, &_enable, &_id); | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | struct proccomm_regulator_drvdata { | 
|  | 84 | struct regulator_desc	rdesc; | 
|  | 85 | int			rise_time; | 
|  | 86 | int			last_voltage; | 
|  | 87 | bool			enabled; | 
|  | 88 | bool			negative; | 
|  | 89 | }; | 
|  | 90 |  | 
|  | 91 | static int proccomm_vreg_enable(struct regulator_dev *rdev) | 
|  | 92 | { | 
|  | 93 | struct proccomm_regulator_drvdata *ddata; | 
|  | 94 | int rc; | 
|  | 95 |  | 
|  | 96 | ddata = rdev_get_drvdata(rdev); | 
|  | 97 | rc = _vreg_switch(rdev_get_id(rdev), VREG_SWITCH_ENABLE); | 
|  | 98 |  | 
|  | 99 | if (rc) { | 
|  | 100 | dev_err(rdev_get_dev(rdev), | 
|  | 101 | "could not enable regulator %d (%s): %d\n", | 
|  | 102 | rdev_get_id(rdev), ddata->rdesc.name, rc); | 
|  | 103 | } else { | 
|  | 104 | dev_dbg(rdev_get_dev(rdev), | 
|  | 105 | "enabled regulator %d (%s)\n", | 
|  | 106 | rdev_get_id(rdev), ddata->rdesc.name); | 
|  | 107 | ddata->enabled = 1; | 
|  | 108 | } | 
|  | 109 |  | 
|  | 110 | return rc; | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | static int proccomm_vreg_disable(struct regulator_dev *rdev) | 
|  | 114 | { | 
|  | 115 | struct proccomm_regulator_drvdata *ddata; | 
|  | 116 | int rc; | 
|  | 117 |  | 
|  | 118 | ddata = rdev_get_drvdata(rdev); | 
|  | 119 | rc = _vreg_switch(rdev_get_id(rdev), VREG_SWITCH_DISABLE); | 
|  | 120 |  | 
|  | 121 | if (rc) { | 
|  | 122 | dev_err(rdev_get_dev(rdev), | 
|  | 123 | "could not disable regulator %d (%s): %d\n", | 
|  | 124 | rdev_get_id(rdev), ddata->rdesc.name, rc); | 
|  | 125 | } else { | 
|  | 126 | dev_dbg(rdev_get_dev(rdev), | 
|  | 127 | "disabled regulator %d (%s)\n", | 
|  | 128 | rdev_get_id(rdev), ddata->rdesc.name); | 
|  | 129 | ddata->enabled = 0; | 
|  | 130 | } | 
|  | 131 |  | 
|  | 132 | return rc; | 
|  | 133 | } | 
|  | 134 |  | 
|  | 135 | static int proccomm_vreg_is_enabled(struct regulator_dev *rdev) | 
|  | 136 | { | 
|  | 137 | struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); | 
|  | 138 |  | 
|  | 139 | return ddata->enabled; | 
|  | 140 | } | 
|  | 141 |  | 
|  | 142 | static int proccomm_vreg_rise_time(struct regulator_dev *rdev) | 
|  | 143 | { | 
|  | 144 | struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); | 
|  | 145 |  | 
|  | 146 | return ddata->rise_time; | 
|  | 147 | } | 
|  | 148 |  | 
|  | 149 | static int proccomm_vreg_get_voltage(struct regulator_dev *rdev) | 
|  | 150 | { | 
|  | 151 |  | 
|  | 152 | struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); | 
|  | 153 |  | 
|  | 154 | return MV_TO_UV(ddata->last_voltage); | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | static int proccomm_vreg_set_voltage(struct regulator_dev *rdev, | 
|  | 158 | int min_uV, int max_uV, unsigned *sel) | 
|  | 159 | { | 
|  | 160 | struct proccomm_regulator_drvdata *ddata = rdev_get_drvdata(rdev); | 
|  | 161 | int level_mV = UV_TO_MV(min_uV); | 
|  | 162 | int rc; | 
|  | 163 |  | 
|  | 164 | rc = _vreg_set_level(rdev_get_id(rdev), | 
|  | 165 | ddata->negative ? -level_mV : level_mV); | 
|  | 166 |  | 
|  | 167 | if (rc) { | 
|  | 168 | dev_err(rdev_get_dev(rdev), | 
|  | 169 | "could not set voltage for regulator %d (%s) " | 
|  | 170 | "to %d mV: %d\n", | 
|  | 171 | rdev_get_id(rdev), ddata->rdesc.name, level_mV, rc); | 
|  | 172 | } else { | 
|  | 173 | dev_dbg(rdev_get_dev(rdev), | 
|  | 174 | "voltage for regulator %d (%s) set to %d mV\n", | 
|  | 175 | rdev_get_id(rdev), ddata->rdesc.name, level_mV); | 
|  | 176 | ddata->last_voltage = level_mV; | 
|  | 177 | } | 
|  | 178 |  | 
|  | 179 | return rc; | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | static struct regulator_ops proccomm_regulator_ops = { | 
|  | 183 | .enable		= proccomm_vreg_enable, | 
|  | 184 | .disable	= proccomm_vreg_disable, | 
|  | 185 | .is_enabled	= proccomm_vreg_is_enabled, | 
|  | 186 | .get_voltage	= proccomm_vreg_get_voltage, | 
|  | 187 | .set_voltage	= proccomm_vreg_set_voltage, | 
|  | 188 | .enable_time	= proccomm_vreg_rise_time, | 
|  | 189 | }; | 
|  | 190 |  | 
|  | 191 | /* | 
|  | 192 | * Create and register a struct regulator_dev based on the information in | 
|  | 193 | * a struct proccomm_regulator_info. | 
|  | 194 | * Fills in the rdev field in struct proccomm_regulator_info. | 
|  | 195 | */ | 
|  | 196 | static struct regulator_dev *__devinit create_proccomm_rdev( | 
|  | 197 | struct proccomm_regulator_info *info, struct device *parent) | 
|  | 198 | { | 
| Mark Brown | 5d028cd | 2011-10-14 13:36:04 +0100 | [diff] [blame] | 199 | const char *name; | 
| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [diff] [blame] | 200 | struct proccomm_regulator_drvdata *d; | 
|  | 201 | struct regulator_dev *rdev; | 
|  | 202 | int rc = 0; | 
|  | 203 |  | 
|  | 204 | if (info->id < 0) { | 
|  | 205 | dev_err(parent, "invalid regulator id %d\n", info->id); | 
|  | 206 | rc = -EINVAL; | 
|  | 207 | goto out; | 
|  | 208 | } | 
|  | 209 |  | 
|  | 210 | name = info->init_data.constraints.name; | 
|  | 211 |  | 
|  | 212 | if (!name) { | 
|  | 213 | dev_err(parent, | 
|  | 214 | "could not register regulator with id %d: " | 
|  | 215 | "no name specified\n", info->id); | 
|  | 216 | rc = -EINVAL; | 
|  | 217 | goto out; | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 | if (info->pulldown > 0) { | 
|  | 221 | rc = _vreg_pull_down(info->id, info->pulldown); | 
|  | 222 | if (rc) { | 
|  | 223 | dev_err(parent, | 
|  | 224 | "probing for regulator %d (%s) failed\n", | 
|  | 225 | info->id, name); | 
|  | 226 | goto out; | 
|  | 227 | } | 
|  | 228 | } | 
|  | 229 |  | 
|  | 230 | d = kzalloc(sizeof(*d), GFP_KERNEL); | 
|  | 231 |  | 
|  | 232 | if (!d) { | 
|  | 233 | dev_err(parent, | 
|  | 234 | "could not allocate struct proccomm_regulator_drvdata " | 
|  | 235 | "for regulator %d (%s)\n", info->id, name); | 
|  | 236 | rc = -ENOMEM; | 
|  | 237 | goto out; | 
|  | 238 | } | 
|  | 239 |  | 
|  | 240 | d->rdesc.name	= name; | 
|  | 241 | d->rdesc.id	= info->id; | 
|  | 242 | d->rdesc.ops	= &proccomm_regulator_ops; | 
|  | 243 | d->rdesc.type	= REGULATOR_VOLTAGE; | 
|  | 244 | d->rdesc.owner	= THIS_MODULE; | 
|  | 245 | d->rise_time	= info->rise_time; | 
|  | 246 | d->enabled	= 0; | 
|  | 247 | d->negative	= info->negative; | 
| Pankaj Kumar | e8db425 | 2012-01-04 13:06:21 +0530 | [diff] [blame] | 248 | d->rdesc.n_voltages = info->n_voltages; | 
| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [diff] [blame] | 249 |  | 
| Rajendra Nayak | 11eafc6 | 2011-11-18 16:47:19 +0530 | [diff] [blame] | 250 | rdev = regulator_register(&d->rdesc, parent, &info->init_data, d, NULL); | 
| Justin Paupore | a6cd209 | 2011-06-22 16:19:45 -0700 | [diff] [blame] | 251 |  | 
|  | 252 | if (IS_ERR(rdev)) { | 
|  | 253 | rc = PTR_ERR(rdev); | 
|  | 254 | dev_err(parent, "error registering regulator %d (%s): %d\n", | 
|  | 255 | info->id, name, rc); | 
|  | 256 | goto clean; | 
|  | 257 | } | 
|  | 258 |  | 
|  | 259 | dev_dbg(parent, "registered regulator %d (%s)\n", info->id, name); | 
|  | 260 |  | 
|  | 261 | return rdev; | 
|  | 262 |  | 
|  | 263 | clean: | 
|  | 264 | kfree(d); | 
|  | 265 | out: | 
|  | 266 | return ERR_PTR(rc); | 
|  | 267 | } | 
|  | 268 |  | 
|  | 269 | /* | 
|  | 270 | * Unregister and destroy a struct regulator_dev created by | 
|  | 271 | * create_proccomm_rdev. | 
|  | 272 | */ | 
|  | 273 | static void destroy_proccomm_rdev(struct regulator_dev *rdev) | 
|  | 274 | { | 
|  | 275 | struct proccomm_regulator_drvdata *d; | 
|  | 276 |  | 
|  | 277 | if (!rdev) | 
|  | 278 | return; | 
|  | 279 |  | 
|  | 280 | d = rdev_get_drvdata(rdev); | 
|  | 281 |  | 
|  | 282 | regulator_unregister(rdev); | 
|  | 283 |  | 
|  | 284 | dev_dbg(rdev_get_dev(rdev)->parent, | 
|  | 285 | "unregistered regulator %d (%s)\n", | 
|  | 286 | d->rdesc.id, d->rdesc.name); | 
|  | 287 |  | 
|  | 288 | kfree(d); | 
|  | 289 | } | 
|  | 290 |  | 
|  | 291 |  | 
|  | 292 | static int __devinit proccomm_vreg_probe(struct platform_device *pdev) | 
|  | 293 | { | 
|  | 294 | struct device *dev = &pdev->dev; | 
|  | 295 | struct proccomm_regulator_platform_data *pdata = dev->platform_data; | 
|  | 296 | struct regulator_dev **rdevs; | 
|  | 297 | int rc = 0; | 
|  | 298 | size_t i = 0; | 
|  | 299 |  | 
|  | 300 | if (!pdata) { | 
|  | 301 | dev_err(dev, "invalid platform data\n"); | 
|  | 302 | rc = -EINVAL; | 
|  | 303 | goto check_fail; | 
|  | 304 | } | 
|  | 305 |  | 
|  | 306 | if (pdata->nregs == 0) { | 
|  | 307 | dev_err(dev, "registering an empty regulator list; " | 
|  | 308 | "this is probably not what you want\n"); | 
|  | 309 | rc = -EINVAL; | 
|  | 310 | goto check_fail; | 
|  | 311 | } | 
|  | 312 |  | 
|  | 313 | rdevs = kcalloc(pdata->nregs, sizeof(*rdevs), GFP_KERNEL); | 
|  | 314 |  | 
|  | 315 | if (!rdevs) { | 
|  | 316 | dev_err(dev, "could not allocate storage for " | 
|  | 317 | "struct regulator_dev array\n"); | 
|  | 318 | rc = -ENOMEM; | 
|  | 319 | goto check_fail; | 
|  | 320 | } | 
|  | 321 |  | 
|  | 322 | platform_set_drvdata(pdev, rdevs); | 
|  | 323 |  | 
|  | 324 | dev_dbg(dev, "registering %d proccomm regulators\n", pdata->nregs); | 
|  | 325 |  | 
|  | 326 | for (i = 0; i < pdata->nregs; i++) { | 
|  | 327 | rdevs[i] = create_proccomm_rdev(&pdata->regs[i], dev); | 
|  | 328 | if (IS_ERR(rdevs[i])) { | 
|  | 329 | rc = PTR_ERR(rdevs[i]); | 
|  | 330 | goto backout; | 
|  | 331 | } | 
|  | 332 | } | 
|  | 333 |  | 
|  | 334 | dev_dbg(dev, "%d proccomm regulators registered\n", pdata->nregs); | 
|  | 335 |  | 
|  | 336 | return rc; | 
|  | 337 |  | 
|  | 338 | backout: | 
|  | 339 | while (--i >= 0) | 
|  | 340 | destroy_proccomm_rdev(rdevs[i]); | 
|  | 341 |  | 
|  | 342 | kfree(rdevs); | 
|  | 343 |  | 
|  | 344 | check_fail: | 
|  | 345 | return rc; | 
|  | 346 | } | 
|  | 347 |  | 
|  | 348 | static int __devexit proccomm_vreg_remove(struct platform_device *pdev) | 
|  | 349 | { | 
|  | 350 | struct proccomm_regulator_platform_data *pdata; | 
|  | 351 | struct regulator_dev **rdevs; | 
|  | 352 | size_t i; | 
|  | 353 |  | 
|  | 354 | pdata = pdev->dev.platform_data; | 
|  | 355 | rdevs = platform_get_drvdata(pdev); | 
|  | 356 |  | 
|  | 357 | for (i = 0; i < pdata->nregs; i++) | 
|  | 358 | destroy_proccomm_rdev(rdevs[i]); | 
|  | 359 |  | 
|  | 360 | kfree(rdevs); | 
|  | 361 |  | 
|  | 362 | return 0; | 
|  | 363 | } | 
|  | 364 |  | 
|  | 365 | static struct platform_driver proccomm_vreg_driver = { | 
|  | 366 | .probe	= proccomm_vreg_probe, | 
|  | 367 | .remove = __devexit_p(proccomm_vreg_remove), | 
|  | 368 | .driver = { | 
|  | 369 | .name	= PROCCOMM_REGULATOR_DEV_NAME, | 
|  | 370 | .owner	= THIS_MODULE, | 
|  | 371 | }, | 
|  | 372 | }; | 
|  | 373 |  | 
|  | 374 | static int __init proccomm_vreg_init(void) | 
|  | 375 | { | 
|  | 376 | return platform_driver_register(&proccomm_vreg_driver); | 
|  | 377 | } | 
|  | 378 | postcore_initcall(proccomm_vreg_init); | 
|  | 379 |  | 
|  | 380 | static void __exit proccomm_vreg_exit(void) | 
|  | 381 | { | 
|  | 382 | platform_driver_unregister(&proccomm_vreg_driver); | 
|  | 383 | } | 
|  | 384 | module_exit(proccomm_vreg_exit); | 
|  | 385 |  | 
|  | 386 | MODULE_LICENSE("GPL v2"); | 
|  | 387 | MODULE_DESCRIPTION("ProcComm regulator driver"); | 
|  | 388 | MODULE_VERSION("1.0"); | 
|  | 389 | MODULE_ALIAS("platform:" PROCCOMM_REGULATOR_DEV_NAME); |