/*
* wm831x-dcdc.c -- DC-DC buck convertor driver for the WM831x series
*
* Copyright 2009 Wolfson Microelectronics PLC.
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/mfd/wm831x/core.h>
#include <linux/mfd/wm831x/regulator.h>
#include <linux/mfd/wm831x/pdata.h>
#define WM831X_BUCKV_MAX_SELECTOR 0x68
#define WM831X_BUCKP_MAX_SELECTOR 0x66
#define WM831X_DCDC_MODE_FAST 0
#define WM831X_DCDC_MODE_NORMAL 1
#define WM831X_DCDC_MODE_IDLE 2
#define WM831X_DCDC_MODE_STANDBY 3
#define WM831X_DCDC_MAX_NAME 9
/* Register offsets in control block */
#define WM831X_DCDC_CONTROL_1 0
#define WM831X_DCDC_CONTROL_2 1
#define WM831X_DCDC_ON_CONFIG 2
#define WM831X_DCDC_SLEEP_CONTROL 3
#define WM831X_DCDC_DVS_CONTROL 4
/*
* Shared
*/
struct wm831x_dcdc {
char name[WM831X_DCDC_MAX_NAME];
char supply_name[WM831X_DCDC_MAX_NAME];
struct regulator_desc desc;
int base;
struct wm831x *wm831x;
struct regulator_dev *regulator;
int dvs_gpio;
int dvs_gpio_state;
int on_vsel;
int dvs_vsel;
};
static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev)
{
struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
struct wm831x *wm831x = dcdc->wm831x;
u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
int val;
val = wm831x_reg_read(wm831x, reg);
if (val < 0)
return val;
val = (val & WM831X_DC1_ON_MODE_MASK) >> WM831X_DC1_ON_MODE_SHIFT;
switch (val) {
case WM831X_DCDC_MODE_FAST:
return REGULATOR_MODE_FAST;
case WM831X_DCDC_MODE_NORMAL:
return REGULATOR_MODE_NORMAL;
case WM831X_DCDC_MODE_STANDBY:
return REGULATOR_MODE_STANDBY;
case WM831X_DCDC_MODE_IDLE:
return REGULATOR_MODE_IDLE;
default:
BUG();
return -EINVAL;
}
}
static int wm831x_dcdc_set_mode_int(struct wm831x *wm831x, int reg,
unsigned int mode)
{
int val;
switch (mode) {
case REGULATOR_MODE_FAST:
val = WM831X_DCDC_MODE_FAST;
break;
case REGULATOR_MODE_NORMAL:
val = WM831X_DCDC_MODE_NORMAL;
break;
case REGULATOR_MODE_STANDBY:
val = WM831X_DCDC_MODE_STANDBY;
break;
case REGULATOR_MODE_IDLE:
val = WM831X_DCDC_MODE_IDLE;
break;
default:
return -EINVAL;
}
return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_MODE_MASK,
val << WM831X_DC1_ON_MODE_SHIFT);
}
static int wm831x_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode)
{
struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
struct wm831x *wm831x = dcdc->wm831x;
u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
return wm831x_dcdc_set_mode_int(wm831x, reg, mode);
}
static int wm831x_dcdc_set_suspend_mode(struct regulator_dev *rdev,
unsigned int mode)
{
struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
struct wm831x *wm831x = dcdc->wm831x;
u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL;
return wm831x_dcdc_set_mode_int(wm831x, reg, mode);
}
static int wm831x_dcdc_get_status(struct regulator_dev *rdev)
{
struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
struct wm831x *wm831x = dcdc->wm831x;
int ret;
/* First, check for errors */
ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS);
if (ret < 0)
return ret;
if (ret & (1 << rdev_get_id(rdev))) {
dev_dbg(wm831x->dev, "DCDC%d under voltage\n",
rdev_get_id(rdev) + 1);
return REGULATOR_STATUS_ERROR;
}
/* DCDC1 and DCDC2 can additionally detect high voltage/current */
if (rdev_get_id(rdev) < 2) {
if (ret & (WM831X_DC1_OV_STS << rdev_get_id(rdev))) {
dev_dbg(wm831x->dev, "DCDC%d over voltage\n",
rdev_get_id(rdev) + 1);
return REGULATOR_STATUS_ERROR;
}
if (ret & (WM831X_DC1_HC_STS << rdev_get_id(rdev))) {