Merge branch 'for-next' of git://git.o-hand.com/linux-mfd

* 'for-next' of git://git.o-hand.com/linux-mfd: (30 commits)
  mfd: Fix section mismatch in da903x
  mfd: move drivers/i2c/chips/menelaus.c to drivers/mfd
  mfd: move drivers/i2c/chips/tps65010.c to drivers/mfd
  mfd: dm355evm msp430 driver
  mfd: Add missing break from wm3850-core
  mfd: Add WM8351 support
  mfd: Support configurable numbers of DCDCs and ISINKs on WM8350
  mfd: Handle missing WM8350 platform data
  mfd: Add WM8352 support
  mfd: Use irq_to_desc in twl4030 code
  power_supply: Add Dialog DA9030 battery charger driver
  mfd: Dialog DA9030 battery charger MFD driver
  mfd: Register WM8400 codec device
  mfd: Pass driver_data onto child devices
  mfd: Fix twl4030-core.c build error
  mfd: twl4030 regulator bug fixes
  mfd: twl4030: create some regulator devices
  mfd: twl4030: cleanup symbols and OMAP dependency
  mfd: twl4030: simplified child creation code
  power_supply: Add battery health reporting for WM8350
  ...
This commit is contained in:
Linus Torvalds 2009-01-05 19:04:09 -08:00
commit 8e128ce331
29 changed files with 4565 additions and 362 deletions

View file

@ -126,19 +126,6 @@ config ISP1301_OMAP
This driver can also be built as a module. If so, the module
will be called isp1301_omap.
config TPS65010
tristate "TPS6501x Power Management chips"
depends on GPIOLIB
default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK
help
If you say yes here you get support for the TPS6501x series of
Power Management chips. These include voltage regulators,
lithium ion/polymer battery charging, and other features that
are often used in portable devices like cell phones and cameras.
This driver can also be built as a module. If so, the module
will be called tps65010.
config SENSORS_MAX6875
tristate "Maxim MAX6875 Power supply supervisor"
depends on EXPERIMENTAL
@ -164,16 +151,6 @@ config SENSORS_TSL2550
This driver can also be built as a module. If so, the module
will be called tsl2550.
config MENELAUS
bool "TWL92330/Menelaus PM chip"
depends on I2C=y && ARCH_OMAP24XX
help
If you say yes here you get support for the Texas Instruments
TWL92330/Menelaus Power Management chip. This include voltage
regulators, Dual slot memory card tranceivers, real-time clock
and other features that are often used in portable devices like
cell phones and PDAs.
config MCU_MPC8349EMITX
tristate "MPC8349E-mITX MCU driver"
depends on I2C && PPC_83xx

View file

@ -19,8 +19,6 @@ obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o
obj-$(CONFIG_PCF8575) += pcf8575.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_MENELAUS) += menelaus.o
obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
obj-$(CONFIG_MCU_MPC8349EMITX) += mcu_mpc8349emitx.o

View file

@ -34,6 +34,14 @@ config MFD_ASIC3
This driver supports the ASIC3 multifunction chip found on many
PDAs (mainly iPAQ and HTC based ones)
config MFD_DM355EVM_MSP
bool "DaVinci DM355 EVM microcontroller"
depends on I2C && MACH_DAVINCI_DM355_EVM
help
This driver supports the MSP430 microcontroller used on these
boards. MSP430 firmware manages resets and power sequencing,
inputs from buttons and the IR remote, LEDs, an RTC, and more.
config HTC_EGPIO
bool "HTC EGPIO support"
depends on GENERIC_HARDIRQS && GPIOLIB && ARM
@ -61,9 +69,32 @@ config UCB1400_CORE
To compile this driver as a module, choose M here: the
module will be called ucb1400_core.
config TPS65010
tristate "TPS6501x Power Management chips"
depends on I2C && GPIOLIB
default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK
help
If you say yes here you get support for the TPS6501x series of
Power Management chips. These include voltage regulators,
lithium ion/polymer battery charging, and other features that
are often used in portable devices like cell phones and cameras.
This driver can also be built as a module. If so, the module
will be called tps65010.
config MENELAUS
bool "Texas Instruments TWL92330/Menelaus PM chip"
depends on I2C=y && ARCH_OMAP24XX
help
If you say yes here you get support for the Texas Instruments
TWL92330/Menelaus Power Management chip. This include voltage
regulators, Dual slot memory card tranceivers, real-time clock
and other features that are often used in portable devices like
cell phones and PDAs.
config TWL4030_CORE
bool "Texas Instruments TWL4030/TPS659x0 Support"
depends on I2C=y && GENERIC_HARDIRQS && (ARCH_OMAP2 || ARCH_OMAP3)
depends on I2C=y && GENERIC_HARDIRQS
help
Say yes here if you have TWL4030 family chip on your board.
This core driver provides register access and IRQ handling
@ -116,6 +147,7 @@ config PMIC_DA903X
config MFD_WM8400
tristate "Support Wolfson Microelectronics WM8400"
select MFD_CORE
depends on I2C
help
Support for the Wolfson Microelecronics WM8400 PMIC and audio
@ -142,6 +174,38 @@ config MFD_WM8350_CONFIG_MODE_3
bool
depends on MFD_WM8350
config MFD_WM8351_CONFIG_MODE_0
bool
depends on MFD_WM8350
config MFD_WM8351_CONFIG_MODE_1
bool
depends on MFD_WM8350
config MFD_WM8351_CONFIG_MODE_2
bool
depends on MFD_WM8350
config MFD_WM8351_CONFIG_MODE_3
bool
depends on MFD_WM8350
config MFD_WM8352_CONFIG_MODE_0
bool
depends on MFD_WM8350
config MFD_WM8352_CONFIG_MODE_1
bool
depends on MFD_WM8350
config MFD_WM8352_CONFIG_MODE_2
bool
depends on MFD_WM8350
config MFD_WM8352_CONFIG_MODE_3
bool
depends on MFD_WM8350
config MFD_WM8350_I2C
tristate "Support Wolfson Microelectronics WM8350 with I2C"
select MFD_WM8350

View file

@ -8,6 +8,8 @@ obj-$(CONFIG_MFD_ASIC3) += asic3.o
obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o
obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o
obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o
@ -17,6 +19,9 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o
obj-$(CONFIG_MFD_WM8350) += wm8350.o
obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_MENELAUS) += menelaus.o
obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
@ -31,4 +36,4 @@ obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o
endif
obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o

View file

@ -151,12 +151,24 @@ int da903x_write(struct device *dev, int reg, uint8_t val)
}
EXPORT_SYMBOL_GPL(da903x_write);
int da903x_writes(struct device *dev, int reg, int len, uint8_t *val)
{
return __da903x_writes(to_i2c_client(dev), reg, len, val);
}
EXPORT_SYMBOL_GPL(da903x_writes);
int da903x_read(struct device *dev, int reg, uint8_t *val)
{
return __da903x_read(to_i2c_client(dev), reg, val);
}
EXPORT_SYMBOL_GPL(da903x_read);
int da903x_reads(struct device *dev, int reg, int len, uint8_t *val)
{
return __da903x_reads(to_i2c_client(dev), reg, len, val);
}
EXPORT_SYMBOL_GPL(da903x_reads);
int da903x_set_bits(struct device *dev, int reg, uint8_t bit_mask)
{
struct da903x_chip *chip = dev_get_drvdata(dev);
@ -435,13 +447,13 @@ static const struct i2c_device_id da903x_id_table[] = {
};
MODULE_DEVICE_TABLE(i2c, da903x_id_table);
static int __devexit __remove_subdev(struct device *dev, void *unused)
static int __remove_subdev(struct device *dev, void *unused)
{
platform_device_unregister(to_platform_device(dev));
return 0;
}
static int __devexit da903x_remove_subdevs(struct da903x_chip *chip)
static int da903x_remove_subdevs(struct da903x_chip *chip)
{
return device_for_each_child(chip->dev, NULL, __remove_subdev);
}

420
drivers/mfd/dm355evm_msp.c Normal file
View file

@ -0,0 +1,420 @@
/*
* dm355evm_msp.c - driver for MSP430 firmware on DM355EVM board
*
* Copyright (C) 2008 David Brownell
*
* 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/init.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/i2c.h>
#include <linux/i2c/dm355evm_msp.h>
/*
* The DM355 is a DaVinci chip with video support but no C64+ DSP. Its
* EVM board has an MSP430 programmed with firmware for various board
* support functions. This driver exposes some of them directly, and
* supports other drivers (e.g. RTC, input) for more complex access.
*
* Because this firmware is entirely board-specific, this file embeds
* knowledge that would be passed as platform_data in a generic driver.
*
* This driver was tested with firmware revision A4.
*/
#if defined(CONFIG_KEYBOARD_DM355EVM) \
|| defined(CONFIG_KEYBOARD_DM355EVM_MODULE)
#define msp_has_keyboard() true
#else
#define msp_has_keyboard() false
#endif
#if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE)
#define msp_has_leds() true
#else
#define msp_has_leds() false
#endif
#if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE)
#define msp_has_rtc() true
#else
#define msp_has_rtc() false
#endif
#if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE)
#define msp_has_tvp() true
#else
#define msp_has_tvp() false
#endif
/*----------------------------------------------------------------------*/
/* REVISIT for paranoia's sake, retry reads/writes on error */
static struct i2c_client *msp430;
/**
* dm355evm_msp_write - Writes a register in dm355evm_msp
* @value: the value to be written
* @reg: register address
*
* Returns result of operation - 0 is success, else negative errno
*/
int dm355evm_msp_write(u8 value, u8 reg)
{
return i2c_smbus_write_byte_data(msp430, reg, value);
}
EXPORT_SYMBOL(dm355evm_msp_write);
/**
* dm355evm_msp_read - Reads a register from dm355evm_msp
* @reg: register address
*
* Returns result of operation - value, or negative errno
*/
int dm355evm_msp_read(u8 reg)
{
return i2c_smbus_read_byte_data(msp430, reg);
}
EXPORT_SYMBOL(dm355evm_msp_read);
/*----------------------------------------------------------------------*/
/*
* Many of the msp430 pins are just used as fixed-direction GPIOs.
* We could export a few more of them this way, if we wanted.
*/
#define MSP_GPIO(bit,reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit))
static const u8 msp_gpios[] = {
/* eight leds */
MSP_GPIO(0, LED), MSP_GPIO(1, LED),
MSP_GPIO(2, LED), MSP_GPIO(3, LED),
MSP_GPIO(4, LED), MSP_GPIO(5, LED),
MSP_GPIO(6, LED), MSP_GPIO(7, LED),
/* SW6 and the NTSC/nPAL jumper */
MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1),
MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1),
MSP_GPIO(4, SWITCH1),
};
#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3)
#define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07)
static int msp_gpio_in(struct gpio_chip *chip, unsigned offset)
{
switch (MSP_GPIO_REG(offset)) {
case DM355EVM_MSP_SWITCH1:
case DM355EVM_MSP_SWITCH2:
case DM355EVM_MSP_SDMMC:
return 0;
default:
return -EINVAL;
}
}
static u8 msp_led_cache;
static int msp_gpio_get(struct gpio_chip *chip, unsigned offset)
{
int reg, status;
reg = MSP_GPIO_REG(offset);
status = dm355evm_msp_read(reg);
if (status < 0)
return status;
if (reg == DM355EVM_MSP_LED)
msp_led_cache = status;
return status & MSP_GPIO_MASK(offset);
}
static int msp_gpio_out(struct gpio_chip *chip, unsigned offset, int value)
{
int mask, bits;
/* NOTE: there are some other signals that could be
* packaged as output GPIOs, but they aren't as useful
* as the LEDs ... so for now we don't.
*/
if (MSP_GPIO_REG(offset) != DM355EVM_MSP_LED)
return -EINVAL;
mask = MSP_GPIO_MASK(offset);
bits = msp_led_cache;
bits &= ~mask;
if (value)
bits |= mask;
msp_led_cache = bits;
return dm355evm_msp_write(bits, DM355EVM_MSP_LED);
}
static void msp_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
msp_gpio_out(chip, offset, value);
}
static struct gpio_chip dm355evm_msp_gpio = {
.label = "dm355evm_msp",
.owner = THIS_MODULE,
.direction_input = msp_gpio_in,
.get = msp_gpio_get,
.direction_output = msp_gpio_out,
.set = msp_gpio_set,
.base = -EINVAL, /* dynamic assignment */
.ngpio = ARRAY_SIZE(msp_gpios),
.can_sleep = true,
};
/*----------------------------------------------------------------------*/
static struct device *add_child(struct i2c_client *client, const char *name,
void *pdata, unsigned pdata_len,
bool can_wakeup, int irq)
{
struct platform_device *pdev;
int status;
pdev = platform_device_alloc(name, -1);
if (!pdev) {
dev_dbg(&client->dev, "can't alloc dev\n");
status = -ENOMEM;
goto err;
}
device_init_wakeup(&pdev->dev, can_wakeup);
pdev->dev.parent = &client->dev;
if (pdata) {
status = platform_device_add_data(pdev, pdata, pdata_len);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add platform_data\n");
goto err;
}
}
if (irq) {
struct resource r = {
.start = irq,
.flags = IORESOURCE_IRQ,
};
status = platform_device_add_resources(pdev, &r, 1);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add irq\n");
goto err;
}
}
status = platform_device_add(pdev);
err:
if (status < 0) {
platform_device_put(pdev);
dev_err(&client->dev, "can't add %s dev\n", name);
return ERR_PTR(status);
}
return &pdev->dev;
}
static int add_children(struct i2c_client *client)
{
static const struct {
int offset;
char *label;
} config_inputs[] = {
/* 8 == right after the LEDs */
{ 8 + 0, "sw6_1", },
{ 8 + 1, "sw6_2", },
{ 8 + 2, "sw6_3", },
{ 8 + 3, "sw6_4", },
{ 8 + 4, "NTSC/nPAL", },
};
struct device *child;
int status;
int i;
/* GPIO-ish stuff */
dm355evm_msp_gpio.dev = &client->dev;
status = gpiochip_add(&dm355evm_msp_gpio);
if (status < 0)
return status;
/* LED output */
if (msp_has_leds()) {
#define GPIO_LED(l) .name = l, .active_low = true
static struct gpio_led evm_leds[] = {
{ GPIO_LED("dm355evm::ds14"),
.default_trigger = "heartbeat", },
{ GPIO_LED("dm355evm::ds15"),
.default_trigger = "mmc0", },
{ GPIO_LED("dm355evm::ds16"),
/* could also be a CE-ATA drive */
.default_trigger = "mmc1", },
{ GPIO_LED("dm355evm::ds17"),
.default_trigger = "nand-disk", },
{ GPIO_LED("dm355evm::ds18"), },
{ GPIO_LED("dm355evm::ds19"), },
{ GPIO_LED("dm355evm::ds20"), },
{ GPIO_LED("dm355evm::ds21"), },
};
#undef GPIO_LED
struct gpio_led_platform_data evm_led_data = {
.num_leds = ARRAY_SIZE(evm_leds),
.leds = evm_leds,
};
for (i = 0; i < ARRAY_SIZE(evm_leds); i++)
evm_leds[i].gpio = i + dm355evm_msp_gpio.base;
/* NOTE: these are the only fully programmable LEDs
* on the board, since GPIO-61/ds22 (and many signals
* going to DC7) must be used for AEMIF address lines
* unless the top 1 GB of NAND is unused...
*/
child = add_child(client, "leds-gpio",
&evm_led_data, sizeof(evm_led_data),
false, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
/* configuration inputs */
for (i = 0; i < ARRAY_SIZE(config_inputs); i++) {
int gpio = dm355evm_msp_gpio.base + config_inputs[i].offset;
gpio_request(gpio, config_inputs[i].label);
gpio_direction_input(gpio);
/* make it easy for userspace to see these */
gpio_export(gpio, false);
}
/* RTC is a 32 bit counter, no alarm */
if (msp_has_rtc()) {
child = add_child(client, "rtc-dm355evm",
NULL, 0, false, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
/* input from buttons and IR remote (uses the IRQ) */
if (msp_has_keyboard()) {
child = add_child(client, "dm355evm_keys",
NULL, 0, true, client->irq);
if (IS_ERR(child))
return PTR_ERR(child);
}
return 0;
}
/*----------------------------------------------------------------------*/
static void dm355evm_command(unsigned command)
{
int status;
status = dm355evm_msp_write(command, DM355EVM_MSP_COMMAND);
if (status < 0)
dev_err(&msp430->dev, "command %d failure %d\n",
command, status);
}
static void dm355evm_power_off(void)
{
dm355evm_command(MSP_COMMAND_POWEROFF);
}
static int dm355evm_msp_remove(struct i2c_client *client)
{
pm_power_off = NULL;
msp430 = NULL;
return 0;
}
static int
dm355evm_msp_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int status;
const char *video = msp_has_tvp() ? "TVP5146" : "imager";
if (msp430)
return -EBUSY;
msp430 = client;
/* display revision status; doubles as sanity check */
status = dm355evm_msp_read(DM355EVM_MSP_FIRMREV);
if (status < 0)
goto fail;
dev_info(&client->dev, "firmware v.%02X, %s as video-in\n",
status, video);
/* mux video input: either tvp5146 or some external imager */
status = dm355evm_msp_write(msp_has_tvp() ? 0 : MSP_VIDEO_IMAGER,
DM355EVM_MSP_VIDEO_IN);
if (status < 0)
dev_warn(&client->dev, "error %d muxing %s as video-in\n",
status, video);
/* init LED cache, and turn off the LEDs */
msp_led_cache = 0xff;
dm355evm_msp_write(msp_led_cache, DM355EVM_MSP_LED);
/* export capabilities we support */
status = add_children(client);
if (status < 0)
goto fail;
/* PM hookup */
pm_power_off = dm355evm_power_off;
return 0;
fail:
/* FIXME remove children ... */
dm355evm_msp_remove(client);
return status;
}
static const struct i2c_device_id dm355evm_msp_ids[] = {
{ "dm355evm_msp", 0 },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(i2c, dm355evm_msp_ids);
static struct i2c_driver dm355evm_msp_driver = {
.driver.name = "dm355evm_msp",
.id_table = dm355evm_msp_ids,
.probe = dm355evm_msp_probe,
.remove = dm355evm_msp_remove,
};
static int __init dm355evm_msp_init(void)
{
return i2c_add_driver(&dm355evm_msp_driver);
}
subsys_initcall(dm355evm_msp_init);
static void __exit dm355evm_msp_exit(void)
{
i2c_del_driver(&dm355evm_msp_driver);
}
module_exit(dm355evm_msp_exit);
MODULE_DESCRIPTION("Interface to MSP430 firmware on DM355EVM");
MODULE_LICENSE("GPL");

View file

@ -34,6 +34,7 @@ static int mfd_add_device(struct device *parent, int id,
goto fail_device;
pdev->dev.parent = parent;
platform_set_drvdata(pdev, cell->driver_data);
ret = platform_device_add_data(pdev,
cell->platform_data, cell->data_size);

View file

@ -33,6 +33,8 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/regulator/machine.h>
#include <linux/i2c.h>
#include <linux/i2c/twl4030.h>
@ -71,6 +73,13 @@
#define twl_has_gpio() false
#endif
#if defined(CONFIG_REGULATOR_TWL4030) \
|| defined(CONFIG_REGULATOR_TWL4030_MODULE)
#define twl_has_regulator() true
#else
#define twl_has_regulator() false
#endif
#if defined(CONFIG_TWL4030_MADC) || defined(CONFIG_TWL4030_MADC_MODULE)
#define twl_has_madc() true
#else
@ -149,6 +158,10 @@
#define HIGH_PERF_SQ (1 << 3)
/* chip-specific feature flags, for i2c_device_id.driver_data */
#define TWL4030_VAUX2 BIT(0) /* pre-5030 voltage ranges */
#define TPS_SUBSET BIT(1) /* tps659[23]0 have fewer LDOs */
/*----------------------------------------------------------------------*/
/* is driver active, bound to a chip? */
@ -225,7 +238,7 @@ static struct twl4030mapping twl4030_map[TWL4030_MODULE_LAST + 1] = {
*
* Returns the result of operation - 0 is success
*/
int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, u8 num_bytes)
int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes)
{
int ret;
int sid;
@ -274,7 +287,7 @@ EXPORT_SYMBOL(twl4030_i2c_write);
*
* Returns result of operation - num_bytes is success else failure.
*/
int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, u8 num_bytes)
int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes)
{
int ret;
u8 val;
@ -352,258 +365,258 @@ EXPORT_SYMBOL(twl4030_i2c_read_u8);
/*----------------------------------------------------------------------*/
static struct device *
add_numbered_child(unsigned chip, const char *name, int num,
void *pdata, unsigned pdata_len,
bool can_wakeup, int irq0, int irq1)
{
struct platform_device *pdev;
struct twl4030_client *twl = &twl4030_modules[chip];
int status;
pdev = platform_device_alloc(name, num);
if (!pdev) {
dev_dbg(&twl->client->dev, "can't alloc dev\n");
status = -ENOMEM;
goto err;
}
device_init_wakeup(&pdev->dev, can_wakeup);
pdev->dev.parent = &twl->client->dev;
if (pdata) {
status = platform_device_add_data(pdev, pdata, pdata_len);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add platform_data\n");
goto err;
}
}
if (irq0) {
struct resource r[2] = {
{ .start = irq0, .flags = IORESOURCE_IRQ, },
{ .start = irq1, .flags = IORESOURCE_IRQ, },
};
status = platform_device_add_resources(pdev, r, irq1 ? 2 : 1);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add irqs\n");
goto err;
}
}
status = platform_device_add(pdev);
err:
if (status < 0) {
platform_device_put(pdev);
dev_err(&twl->client->dev, "can't add %s dev\n", name);
return ERR_PTR(status);
}
return &pdev->dev;
}
static inline struct device *add_child(unsigned chip, const char *name,
void *pdata, unsigned pdata_len,
bool can_wakeup, int irq0, int irq1)
{
return add_numbered_child(chip, name, -1, pdata, pdata_len,
can_wakeup, irq0, irq1);
}
static struct device *
add_regulator_linked(int num, struct regulator_init_data *pdata,
struct regulator_consumer_supply *consumers,
unsigned num_consumers)
{
/* regulator framework demands init_data ... */
if (!pdata)
return NULL;
if (consumers) {
pdata->consumer_supplies = consumers;
pdata->num_consumer_supplies = num_consumers;
}
/* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */
return add_numbered_child(3, "twl4030_reg", num,
pdata, sizeof(*pdata), false, 0, 0);
}
static struct device *
add_regulator(int num, struct regulator_init_data *pdata)
{
return add_regulator_linked(num, pdata, NULL, 0);
}
/*
* NOTE: We know the first 8 IRQs after pdata->base_irq are
* for the PIH, and the next are for the PWR_INT SIH, since
* that's how twl_init_irq() sets things up.
*/
static int add_children(struct twl4030_platform_data *pdata)
static int
add_children(struct twl4030_platform_data *pdata, unsigned long features)
{
struct platform_device *pdev = NULL;
struct twl4030_client *twl = NULL;
int status = 0;
struct device *child;
struct device *usb_transceiver = NULL;
if (twl_has_bci() && pdata->bci) {
twl = &twl4030_modules[3];
pdev = platform_device_alloc("twl4030_bci", -1);
if (!pdev) {
pr_debug("%s: can't alloc bci dev\n", DRIVER_NAME);
status = -ENOMEM;
goto err;
}
if (status == 0) {
pdev->dev.parent = &twl->client->dev;
status = platform_device_add_data(pdev, pdata->bci,
sizeof(*pdata->bci));
if (status < 0) {
dev_dbg(&twl->client->dev,
"can't add bci data, %d\n",
status);
goto err;
}
}
if (status == 0) {
struct resource r = {
.start = pdata->irq_base + 8 + 1,
.flags = IORESOURCE_IRQ,
};
status = platform_device_add_resources(pdev, &r, 1);
}
if (status == 0)
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create bci dev, %d\n",
status);
goto err;
}
if (twl_has_bci() && pdata->bci && !(features & TPS_SUBSET)) {
child = add_child(3, "twl4030_bci",
pdata->bci, sizeof(*pdata->bci),
false,
/* irq0 = CHG_PRES, irq1 = BCI */
pdata->irq_base + 8 + 1, pdata->irq_base + 2);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_gpio() && pdata->gpio) {
twl = &twl4030_modules[1];
pdev = platform_device_alloc("twl4030_gpio", -1);
if (!pdev) {
pr_debug("%s: can't alloc gpio dev\n", DRIVER_NAME);
status = -ENOMEM;
goto err;
}
/* more driver model init */
if (status == 0) {
pdev->dev.parent = &twl->client->dev;
/* device_init_wakeup(&pdev->dev, 1); */
status = platform_device_add_data(pdev, pdata->gpio,
sizeof(*pdata->gpio));
if (status < 0) {
dev_dbg(&twl->client->dev,
"can't add gpio data, %d\n",
status);
goto err;
}
}
/* GPIO module IRQ */
if (status == 0) {
struct resource r = {
.start = pdata->irq_base + 0,
.flags = IORESOURCE_IRQ,
};
status = platform_device_add_resources(pdev, &r, 1);
}
if (status == 0)
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create gpio dev, %d\n",
status);
goto err;
}
child = add_child(1, "twl4030_gpio",
pdata->gpio, sizeof(*pdata->gpio),
false, pdata->irq_base + 0, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_keypad() && pdata->keypad) {
pdev = platform_device_alloc("twl4030_keypad", -1);
if (pdev) {
twl = &twl4030_modules[2];
pdev->dev.parent = &twl->client->dev;
device_init_wakeup(&pdev->dev, 1);
status = platform_device_add_data(pdev, pdata->keypad,
sizeof(*pdata->keypad));
if (status < 0) {
dev_dbg(&twl->client->dev,
"can't add keypad data, %d\n",
status);
platform_device_put(pdev);
goto err;
}
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create keypad dev, %d\n",
status);
goto err;
}
} else {
pr_debug("%s: can't alloc keypad dev\n", DRIVER_NAME);
status = -ENOMEM;
goto err;
}
child = add_child(2, "twl4030_keypad",
pdata->keypad, sizeof(*pdata->keypad),
true, pdata->irq_base + 1, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_madc() && pdata->madc) {
pdev = platform_device_alloc("twl4030_madc", -1);
if (pdev) {
twl = &twl4030_modules[2];
pdev->dev.parent = &twl->client->dev;
device_init_wakeup(&pdev->dev, 1);
status = platform_device_add_data(pdev, pdata->madc,
sizeof(*pdata->madc));
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't add madc data, %d\n",
status);
goto err;
}
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create madc dev, %d\n",
status);
goto err;
}
} else {
pr_debug("%s: can't alloc madc dev\n", DRIVER_NAME);
status = -ENOMEM;
goto err;
}
child = add_child(2, "twl4030_madc",
pdata->madc, sizeof(*pdata->madc),
true, pdata->irq_base + 3, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_rtc()) {
twl = &twl4030_modules[3];
pdev = platform_device_alloc("twl4030_rtc", -1);
if (!pdev) {
pr_debug("%s: can't alloc rtc dev\n", DRIVER_NAME);
status = -ENOMEM;
} else {
pdev->dev.parent = &twl->client->dev;
device_init_wakeup(&pdev->dev, 1);
}
/*
* REVISIT platform_data here currently might use of
* REVISIT platform_data here currently might expose the
* "msecure" line ... but for now we just expect board
* setup to tell the chip "we are secure" at all times.
* setup to tell the chip "it's always ok to SET_TIME".
* Eventually, Linux might become more aware of such
* HW security concerns, and "least privilege".
*/
/* RTC module IRQ */
if (status == 0) {
struct resource r = {
.start = pdata->irq_base + 8 + 3,
.flags = IORESOURCE_IRQ,
};
status = platform_device_add_resources(pdev, &r, 1);
}
if (status == 0)
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create rtc dev, %d\n",
status);
goto err;
}
child = add_child(3, "twl4030_rtc",
NULL, 0,
true, pdata->irq_base + 8 + 3, 0);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_usb() && pdata->usb) {
twl = &twl4030_modules[0];
child = add_child(0, "twl4030_usb",
pdata->usb, sizeof(*pdata->usb),
true,
/* irq0 = USB_PRES, irq1 = USB */
pdata->irq_base + 8 + 2, pdata->irq_base + 4);
if (IS_ERR(child))
return PTR_ERR(child);
pdev = platform_device_alloc("twl4030_usb", -1);
if (!pdev) {
pr_debug("%s: can't alloc usb dev\n", DRIVER_NAME);
status = -ENOMEM;
goto err;
}
if (status == 0) {
pdev->dev.parent = &twl->client->dev;
device_init_wakeup(&pdev->dev, 1);
status = platform_device_add_data(pdev, pdata->usb,
sizeof(*pdata->usb));
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't add usb data, %d\n",
status);
goto err;
}
}
if (status == 0) {
struct resource r = {
.start = pdata->irq_base + 8 + 2,
.flags = IORESOURCE_IRQ,
};
status = platform_device_add_resources(pdev, &r, 1);
}
if (status == 0)
status = platform_device_add(pdev);
if (status < 0) {
platform_device_put(pdev);
dev_dbg(&twl->client->dev,
"can't create usb dev, %d\n",
status);
}
/* we need to connect regulators to this transceiver */
usb_transceiver = child;
}
err:
if (status)
pr_err("failed to add twl4030's children (status %d)\n", status);
return status;
if (twl_has_regulator()) {
/*
child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1);
if (IS_ERR(child))
return PTR_ERR(child);
*/
child = add_regulator(TWL4030_REG_VMMC1, pdata->vmmc1);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator(TWL4030_REG_VDAC, pdata->vdac);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator((features & TWL4030_VAUX2)
? TWL4030_REG_VAUX2_4030
: TWL4030_REG_VAUX2,
pdata->vaux2);
if (IS_ERR(child))
return PTR_ERR(child);
}
if (twl_has_regulator() && usb_transceiver) {
static struct regulator_consumer_supply usb1v5 = {
.supply = "usb1v5",
};
static struct regulator_consumer_supply usb1v8 = {
.supply = "usb1v8",
};
static struct regulator_consumer_supply usb3v1 = {
.supply = "usb3v1",
};
/* this is a template that gets copied */
struct regulator_init_data usb_fixed = {
.constraints.valid_modes_mask =
REGULATOR_MODE_NORMAL
| REGULATOR_MODE_STANDBY,
.constraints.valid_ops_mask =
REGULATOR_CHANGE_MODE
| REGULATOR_CHANGE_STATUS,
};
usb1v5.dev = usb_transceiver;
usb1v8.dev = usb_transceiver;
usb3v1.dev = usb_transceiver;
child = add_regulator_linked(TWL4030_REG_VUSB1V5, &usb_fixed,
&usb1v5, 1);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator_linked(TWL4030_REG_VUSB1V8, &usb_fixed,
&usb1v8, 1);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator_linked(TWL4030_REG_VUSB3V1, &usb_fixed,
&usb3v1, 1);
if (IS_ERR(child))
return PTR_ERR(child);
}
/* maybe add LDOs that are omitted on cost-reduced parts */
if (twl_has_regulator() && !(features & TPS_SUBSET)) {
/*
child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2);
if (IS_ERR(child))
return PTR_ERR(child);
*/
child = add_regulator(TWL4030_REG_VMMC2, pdata->vmmc2);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator(TWL4030_REG_VSIM, pdata->vsim);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator(TWL4030_REG_VAUX1, pdata->vaux1);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator(TWL4030_REG_VAUX3, pdata->vaux3);
if (IS_ERR(child))
return PTR_ERR(child);
child = add_regulator(TWL4030_REG_VAUX4, pdata->vaux4);
if (IS_ERR(child))
return PTR_ERR(child);
}
return 0;
}
/*----------------------------------------------------------------------*/
@ -645,12 +658,7 @@ static void __init clocks_init(void)
osc = clk_get(NULL, "osc_ck");
else
osc = clk_get(NULL, "osc_sys_ck");
#else
/* REVISIT for non-OMAP systems, pass the clock rate from
* board init code, using platform_data.
*/
osc = ERR_PTR(-EIO);
#endif
if (IS_ERR(osc)) {
printk(KERN_WARNING "Skipping twl4030 internal clock init and "
"using bootloader value (unknown osc rate)\n");
@ -660,6 +668,18 @@ static void __init clocks_init(void)
rate = clk_get_rate(osc);
clk_put(osc);
#else
/* REVISIT for non-OMAP systems, pass the clock rate from
* board init code, using platform_data.
*/
osc = ERR_PTR(-EIO);
printk(KERN_WARNING "Skipping twl4030 internal clock init and "
"using bootloader value (unknown osc rate)\n");
return;
#endif
switch (rate) {
case 19200000:
ctrl = HFCLK_FREQ_19p2_MHZ;
@ -764,7 +784,7 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id)
goto fail;
}
status = add_children(pdata);
status = add_children(pdata, id->driver_data);
fail:
if (status < 0)
twl4030_remove(client);
@ -772,11 +792,11 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id)
}
static const struct i2c_device_id twl4030_ids[] = {
{ "twl4030", 0 }, /* "Triton 2" */
{ "tps65950", 0 }, /* catalog version of twl4030 */
{ "tps65930", 0 }, /* fewer LDOs and DACs; no charger */
{ "tps65920", 0 }, /* fewer LDOs; no codec or charger */
{ "twl5030", 0 }, /* T2 updated */
{ "twl4030", TWL4030_VAUX2 }, /* "Triton 2" */
{ "twl5030", 0 }, /* T2 updated */
{ "tps65950", 0 }, /* catalog version of twl5030 */
{ "tps65930", TPS_SUBSET }, /* fewer LDOs and DACs; no charger */
{ "tps65920", TPS_SUBSET }, /* fewer LDOs; no codec or charger */
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(i2c, twl4030_ids);

View file

@ -180,10 +180,15 @@ static struct completion irq_event;
static int twl4030_irq_thread(void *data)
{
long irq = (long)data;
irq_desc_t *desc = irq_desc + irq;
struct irq_desc *desc = irq_to_desc(irq);
static unsigned i2c_errors;
const static unsigned max_i2c_errors = 100;
if (!desc) {
pr_err("twl4030: Invalid IRQ: %ld\n", irq);
return -EINVAL;
}
current->flags |= PF_NOFREEZE;
while (!kthread_should_stop()) {
@ -215,7 +220,13 @@ static int twl4030_irq_thread(void *data)
pih_isr;
pih_isr >>= 1, module_irq++) {
if (pih_isr & 0x1) {
irq_desc_t *d = irq_desc + module_irq;
struct irq_desc *d = irq_to_desc(module_irq);
if (!d) {
pr_err("twl4030: Invalid SIH IRQ: %d\n",
module_irq);
return -EINVAL;
}
/* These can't be masked ... always warn
* if we get any surprises.
@ -452,10 +463,16 @@ static void twl4030_sih_do_edge(struct work_struct *work)
/* Modify only the bits we know must change */
while (edge_change) {
int i = fls(edge_change) - 1;
struct irq_desc *d = irq_desc + i + agent->irq_base;
struct irq_desc *d = irq_to_desc(i + agent->irq_base);
int byte = 1 + (i >> 2);
int off = (i & 0x3) * 2;
if (!d) {
pr_err("twl4030: Invalid IRQ: %d\n",
i + agent->irq_base);
return;
}
bytes[byte] &= ~(0x03 << off);
spin_lock_irq(&d->lock);
@ -512,9 +529,14 @@ static void twl4030_sih_unmask(unsigned irq)
static int twl4030_sih_set_type(unsigned irq, unsigned trigger)
{
struct sih_agent *sih = get_irq_chip_data(irq);
struct irq_desc *desc = irq_desc + irq;
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc) {
pr_err("twl4030: Invalid IRQ: %d\n", irq);
return -EINVAL;
}
if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
return -EINVAL;

View file

@ -63,7 +63,6 @@
*/
static DEFINE_MUTEX(io_mutex);
static DEFINE_MUTEX(reg_lock_mutex);
static DEFINE_MUTEX(auxadc_mutex);
/* Perform a physical read from the device.
*/
@ -299,6 +298,13 @@ int wm8350_block_write(struct wm8350 *wm8350, int start_reg, int regs,
}
EXPORT_SYMBOL_GPL(wm8350_block_write);
/**
* wm8350_reg_lock()
*
* The WM8350 has a hardware lock which can be used to prevent writes to
* some registers (generally those which can cause particularly serious
* problems if misused). This function enables that lock.
*/
int wm8350_reg_lock(struct wm8350 *wm8350)
{
u16 key = WM8350_LOCK_KEY;
@ -314,6 +320,15 @@ int wm8350_reg_lock(struct wm8350 *wm8350)
}
EXPORT_SYMBOL_GPL(wm8350_reg_lock);
/**
* wm8350_reg_unlock()
*
* The WM8350 has a hardware lock which can be used to prevent writes to
* some registers (generally those which can cause particularly serious
* problems if misused). This function disables that lock so updates
* can be performed. For maximum safety this should be done only when
* required.
*/
int wm8350_reg_unlock(struct wm8350 *wm8350)
{
u16 key = WM8350_UNLOCK_KEY;
@ -1066,38 +1081,158 @@ int wm8350_unmask_irq(struct wm8350 *wm8350, int irq)
}
EXPORT_SYMBOL_GPL(wm8350_unmask_irq);
int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref)
{
u16 reg, result = 0;
int tries = 5;
if (channel < WM8350_AUXADC_AUX1 || channel > WM8350_AUXADC_TEMP)
return -EINVAL;
if (channel >= WM8350_AUXADC_USB && channel <= WM8350_AUXADC_TEMP
&& (scale != 0 || vref != 0))
return -EINVAL;
mutex_lock(&wm8350->auxadc_mutex);
/* Turn on the ADC */
reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5);
wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg | WM8350_AUXADC_ENA);
if (scale || vref) {
reg = scale << 13;
reg |= vref << 12;
wm8350_reg_write(wm8350, WM8350_AUX1_READBACK + channel, reg);
}
reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1);
reg |= 1 << channel | WM8350_AUXADC_POLL;
wm8350_reg_write(wm8350, WM8350_DIGITISER_CONTROL_1, reg);
do {
schedule_timeout_interruptible(1);
reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1);
} while (tries-- && (reg & WM8350_AUXADC_POLL));
if (!tries)
dev_err(wm8350->dev, "adc chn %d read timeout\n", channel);
else
result = wm8350_reg_read(wm8350,
WM8350_AUX1_READBACK + channel);
/* Turn off the ADC */
reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5);
wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5,
reg & ~WM8350_AUXADC_ENA);
mutex_unlock(&wm8350->auxadc_mutex);
return result & WM8350_AUXADC_DATA1_MASK;
}
EXPORT_SYMBOL_GPL(wm8350_read_auxadc);
/*
* Cache is always host endian.
*/
static int wm8350_create_cache(struct wm8350 *wm8350, int mode)
static int wm8350_create_cache(struct wm8350 *wm8350, int type, int mode)
{
int i, ret = 0;
u16 value;
const u16 *reg_map;
switch (mode) {
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_0
switch (type) {
case 0:
reg_map = wm8350_mode0_defaults;
break;
switch (mode) {
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_0
case 0:
reg_map = wm8350_mode0_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_1
case 1:
reg_map = wm8350_mode1_defaults;
break;
case 1:
reg_map = wm8350_mode1_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_2
case 2:
reg_map = wm8350_mode2_defaults;
break;
case 2:
reg_map = wm8350_mode2_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_3
case 3:
reg_map = wm8350_mode3_defaults;
break;
case 3:
reg_map = wm8350_mode3_defaults;
break;
#endif
default:
dev_err(wm8350->dev,
"WM8350 configuration mode %d not supported\n",
mode);
return -EINVAL;
}
break;
case 1:
switch (mode) {
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_0
case 0:
reg_map = wm8351_mode0_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_1
case 1:
reg_map = wm8351_mode1_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_2
case 2:
reg_map = wm8351_mode2_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_3
case 3:
reg_map = wm8351_mode3_defaults;
break;
#endif
default:
dev_err(wm8350->dev,
"WM8351 configuration mode %d not supported\n",
mode);
return -EINVAL;
}
break;
case 2:
switch (mode) {
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_0
case 0:
reg_map = wm8352_mode0_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_1
case 1:
reg_map = wm8352_mode1_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_2
case 2:
reg_map = wm8352_mode2_defaults;
break;
#endif
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_3
case 3:
reg_map = wm8352_mode3_defaults;
break;
#endif
default:
dev_err(wm8350->dev,
"WM8352 configuration mode %d not supported\n",
mode);
return -EINVAL;
}
break;
default:
dev_err(wm8350->dev, "Configuration mode %d not supported\n",
dev_err(wm8350->dev,
"WM835x configuration mode %d not supported\n",
mode);
return -EINVAL;
}
@ -1163,53 +1298,113 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
struct wm8350_platform_data *pdata)
{
int ret = -EINVAL;
u16 id1, id2, mask, mode;
u16 id1, id2, mask_rev;
u16 cust_id, mode, chip_rev;
/* get WM8350 revision and config mode */
wm8350->read_dev(wm8350, WM8350_RESET_ID, sizeof(id1), &id1);
wm8350->read_dev(wm8350, WM8350_ID, sizeof(id2), &id2);
wm8350->read_dev(wm8350, WM8350_REVISION, sizeof(mask_rev), &mask_rev);
id1 = be16_to_cpu(id1);
id2 = be16_to_cpu(id2);
mask_rev = be16_to_cpu(mask_rev);
if (id1 == 0x6143) {
switch ((id2 & WM8350_CHIP_REV_MASK) >> 12) {
case WM8350_REV_E:
dev_info(wm8350->dev, "Found Rev E device\n");
wm8350->rev = WM8350_REV_E;
break;
case WM8350_REV_F:
dev_info(wm8350->dev, "Found Rev F device\n");
wm8350->rev = WM8350_REV_F;
break;
case WM8350_REV_G:
dev_info(wm8350->dev, "Found Rev G device\n");
wm8350->rev = WM8350_REV_G;
break;
default:
/* For safety we refuse to run on unknown hardware */
dev_info(wm8350->dev, "Found unknown rev\n");
ret = -ENODEV;
goto err;
}
} else {
dev_info(wm8350->dev, "Device with ID %x is not a WM8350\n",
id1);
if (id1 != 0x6143) {
dev_err(wm8350->dev,
"Device with ID %x is not a WM8350\n", id1);
ret = -ENODEV;
goto err;
}
mode = id2 & WM8350_CONF_STS_MASK >> 10;
mask = id2 & WM8350_CUST_ID_MASK;
dev_info(wm8350->dev, "Config mode %d, ROM mask %d\n", mode, mask);
cust_id = id2 & WM8350_CUST_ID_MASK;
chip_rev = (id2 & WM8350_CHIP_REV_MASK) >> 12;
dev_info(wm8350->dev,
"CONF_STS %d, CUST_ID %d, MASK_REV %d, CHIP_REV %d\n",
mode, cust_id, mask_rev, chip_rev);
ret = wm8350_create_cache(wm8350, mode);
if (cust_id != 0) {
dev_err(wm8350->dev, "Unsupported CUST_ID\n");
ret = -ENODEV;
goto err;
}
switch (mask_rev) {
case 0:
wm8350->pmic.max_dcdc = WM8350_DCDC_6;
wm8350->pmic.max_isink = WM8350_ISINK_B;
switch (chip_rev) {
case WM8350_REV_E:
dev_info(wm8350->dev, "WM8350 Rev E\n");
break;
case WM8350_REV_F:
dev_info(wm8350->dev, "WM8350 Rev F\n");
break;
case WM8350_REV_G:
dev_info(wm8350->dev, "WM8350 Rev G\n");
wm8350->power.rev_g_coeff = 1;
break;
case WM8350_REV_H:
dev_info(wm8350->dev, "WM8350 Rev H\n");
wm8350->power.rev_g_coeff = 1;
break;
default:
/* For safety we refuse to run on unknown hardware */
dev_err(wm8350->dev, "Unknown WM8350 CHIP_REV\n");
ret = -ENODEV;
goto err;
}
break;
case 1:
wm8350->pmic.max_dcdc = WM8350_DCDC_4;
wm8350->pmic.max_isink = WM8350_ISINK_A;
switch (chip_rev) {
case 0:
dev_info(wm8350->dev, "WM8351 Rev A\n");
wm8350->power.rev_g_coeff = 1;
break;
default:
dev_err(wm8350->dev, "Unknown WM8351 CHIP_REV\n");
ret = -ENODEV;
goto err;
}
break;
case 2:
wm8350->pmic.max_dcdc = WM8350_DCDC_6;
wm8350->pmic.max_isink = WM8350_ISINK_B;
switch (chip_rev) {
case 0:
dev_info(wm8350->dev, "WM8352 Rev A\n");
wm8350->power.rev_g_coeff = 1;
break;
default:
dev_err(wm8350->dev, "Unknown WM8352 CHIP_REV\n");
ret = -ENODEV;
goto err;
}
break;
default:
dev_err(wm8350->dev, "Unknown MASK_REV\n");
ret = -ENODEV;
goto err;
}
ret = wm8350_create_cache(wm8350, mask_rev, mode);
if (ret < 0) {
printk(KERN_ERR "wm8350: failed to create register cache\n");
dev_err(wm8350->dev, "Failed to create register cache\n");
return ret;
}
if (pdata->init) {
if (pdata && pdata->init) {
ret = pdata->init(wm8350);
if (ret != 0) {
dev_err(wm8350->dev, "Platform init() failed: %d\n",
@ -1218,6 +1413,7 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
}
}
mutex_init(&wm8350->auxadc_mutex);
mutex_init(&wm8350->irq_mutex);
INIT_WORK(&wm8350->irq_work, wm8350_irq_worker);
if (irq) {

View file

@ -1,8 +1,6 @@
/*
* wm8350-i2c.c -- Generic I2C driver for Wolfson WM8350 PMIC
*
* This driver defines and configures the WM8350 for the Freescale i.MX32ADS.
*
* Copyright 2007, 2008 Wolfson Microelectronics PLC.
*
* Author: Liam Girdwood
@ -99,6 +97,8 @@ static int wm8350_i2c_remove(struct i2c_client *i2c)
static const struct i2c_device_id wm8350_i2c_id[] = {
{ "wm8350", 0 },
{ "wm8351", 0 },
{ "wm8352", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, wm8350_i2c_id);

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@
#include <linux/bug.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/mfd/core.h>
#include <linux/mfd/wm8400-private.h>
#include <linux/mfd/wm8400-audio.h>
@ -239,6 +240,16 @@ void wm8400_reset_codec_reg_cache(struct wm8400 *wm8400)
}
EXPORT_SYMBOL_GPL(wm8400_reset_codec_reg_cache);
static int wm8400_register_codec(struct wm8400 *wm8400)
{
struct mfd_cell cell = {
.name = "wm8400-codec",
.driver_data = wm8400,
};
return mfd_add_devices(wm8400->dev, -1, &cell, 1, NULL, 0);
}
/*
* wm8400_init - Generic initialisation
*
@ -296,24 +307,32 @@ static int wm8400_init(struct wm8400 *wm8400,
reg = (reg & WM8400_CHIP_REV_MASK) >> WM8400_CHIP_REV_SHIFT;
dev_info(wm8400->dev, "WM8400 revision %x\n", reg);
ret = wm8400_register_codec(wm8400);
if (ret != 0) {
dev_err(wm8400->dev, "Failed to register codec\n");
goto err_children;
}
if (pdata && pdata->platform_init) {
ret = pdata->platform_init(wm8400->dev);
if (ret != 0)
if (ret != 0) {
dev_err(wm8400->dev, "Platform init failed: %d\n",
ret);
goto err_children;
}
} else
dev_warn(wm8400->dev, "No platform initialisation supplied\n");
return 0;
err_children:
mfd_remove_devices(wm8400->dev);
return ret;
}
static void wm8400_release(struct wm8400 *wm8400)
{
int i;
for (i = 0; i < ARRAY_SIZE(wm8400->regulators); i++)
if (wm8400->regulators[i].name)
platform_device_unregister(&wm8400->regulators[i]);
mfd_remove_devices(wm8400->dev);
}
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)

View file

@ -29,6 +29,13 @@ config APM_POWER
Say Y here to enable support APM status emulation using
battery class devices.
config WM8350_POWER
tristate "WM8350 PMU support"
depends on MFD_WM8350
help
Say Y here to enable support for the power management unit
provided by the Wolfson Microelectronics WM8350 PMIC.
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
select W1
@ -68,4 +75,11 @@ config BATTERY_BQ27x00
help
Say Y here to enable support for batteries with BQ27200(I2C) chip.
config BATTERY_DA9030
tristate "DA9030 battery driver"
depends on PMIC_DA903X
help
Say Y here to enable support for batteries charger integrated into
DA9030 PMIC.
endif # POWER_SUPPLY

View file

@ -16,6 +16,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
obj-$(CONFIG_PDA_POWER) += pda_power.o
obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
@ -23,3 +24,4 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o

View file

@ -0,0 +1,600 @@
/*
* Battery charger driver for Dialog Semiconductor DA9030
*
* Copyright (C) 2008 Compulab, Ltd.
* Mike Rapoport <mike@compulab.co.il>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/da903x.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#define DA9030_STATUS_CHDET (1 << 3)
#define DA9030_FAULT_LOG 0x0a
#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7)
#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4)
#define DA9030_CHARGE_CONTROL 0x28
#define DA9030_CHRG_CHARGER_ENABLE (1 << 7)
#define DA9030_ADC_MAN_CONTROL 0x30
#define DA9030_ADC_TBATREF_ENABLE (1 << 5)
#define DA9030_ADC_LDO_INT_ENABLE (1 << 4)
#define DA9030_ADC_AUTO_CONTROL 0x31
#define DA9030_ADC_TBAT_ENABLE (1 << 5)
#define DA9030_ADC_VBAT_IN_TXON (1 << 4)
#define DA9030_ADC_VCH_ENABLE (1 << 3)
#define DA9030_ADC_ICH_ENABLE (1 << 2)
#define DA9030_ADC_VBAT_ENABLE (1 << 1)
#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0)
#define DA9030_VBATMON 0x32
#define DA9030_VBATMONTXON 0x33
#define DA9030_TBATHIGHP 0x34
#define DA9030_TBATHIGHN 0x35
#define DA9030_TBATLOW 0x36
#define DA9030_VBAT_RES 0x41
#define DA9030_VBATMIN_RES 0x42
#define DA9030_VBATMINTXON_RES 0x43
#define DA9030_ICHMAX_RES 0x44
#define DA9030_ICHMIN_RES 0x45
#define DA9030_ICHAVERAGE_RES 0x46
#define DA9030_VCHMAX_RES 0x47
#define DA9030_VCHMIN_RES 0x48
#define DA9030_TBAT_RES 0x49
struct da9030_adc_res {
uint8_t vbat_res;
uint8_t vbatmin_res;
uint8_t vbatmintxon;
uint8_t ichmax_res;
uint8_t ichmin_res;
uint8_t ichaverage_res;
uint8_t vchmax_res;
uint8_t vchmin_res;
uint8_t tbat_res;
uint8_t adc_in4_res;
uint8_t adc_in5_res;
};
struct da9030_battery_thresholds {
int tbat_low;
int tbat_high;
int tbat_restart;
int vbat_low;
int vbat_crit;
int vbat_charge_start;
int vbat_charge_stop;
int vbat_charge_restart;
int vcharge_min;
int vcharge_max;
};
struct da9030_charger {
struct power_supply psy;
struct device *master;
struct da9030_adc_res adc;
struct delayed_work work;
unsigned int interval;
struct power_supply_info *battery_info;
struct da9030_battery_thresholds thresholds;
unsigned int charge_milliamp;
unsigned int charge_millivolt;
/* charger status */
bool chdet;
uint8_t fault;
int mA;
int mV;
bool is_on;
struct notifier_block nb;
/* platform callbacks for battery low and critical events */
void (*battery_low)(void);
void (*battery_critical)(void);
struct dentry *debug_file;
};
static inline int da9030_reg_to_mV(int reg)
{
return ((reg * 2650) >> 8) + 2650;
}
static inline int da9030_millivolt_to_reg(int mV)
{
return ((mV - 2650) << 8) / 2650;
}
static inline int da9030_reg_to_mA(int reg)
{
return ((reg * 24000) >> 8) / 15;
}
#ifdef CONFIG_DEBUG_FS
static int bat_debug_show(struct seq_file *s, void *data)
{
struct da9030_charger *charger = s->private;
seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off");
if (charger->chdet) {
seq_printf(s, "iset = %dmA, vset = %dmV\n",
charger->mA, charger->mV);
}
seq_printf(s, "vbat_res = %d (%dmV)\n",
charger->adc.vbat_res,
da9030_reg_to_mV(charger->adc.vbat_res));
seq_printf(s, "vbatmin_res = %d (%dmV)\n",
charger->adc.vbatmin_res,
da9030_reg_to_mV(charger->adc.vbatmin_res));
seq_printf(s, "vbatmintxon = %d (%dmV)\n",
charger->adc.vbatmintxon,
da9030_reg_to_mV(charger->adc.vbatmintxon));
seq_printf(s, "ichmax_res = %d (%dmA)\n",
charger->adc.ichmax_res,
da9030_reg_to_mV(charger->adc.ichmax_res));
seq_printf(s, "ichmin_res = %d (%dmA)\n",
charger->adc.ichmin_res,
da9030_reg_to_mA(charger->adc.ichmin_res));
seq_printf(s, "ichaverage_res = %d (%dmA)\n",
charger->adc.ichaverage_res,
da9030_reg_to_mA(charger->adc.ichaverage_res));
seq_printf(s, "vchmax_res = %d (%dmV)\n",
charger->adc.vchmax_res,
da9030_reg_to_mA(charger->adc.vchmax_res));
seq_printf(s, "vchmin_res = %d (%dmV)\n",
charger->adc.vchmin_res,
da9030_reg_to_mV(charger->adc.vchmin_res));
return 0;
}
static int debug_open(struct inode *inode, struct file *file)
{
return single_open(file, bat_debug_show, inode->i_private);
}
static const struct file_operations bat_debug_fops = {
.open = debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
{
charger->debug_file = debugfs_create_file("charger", 0666, 0, charger,
&bat_debug_fops);
return charger->debug_file;
}
static void da9030_bat_remove_debugfs(struct da9030_charger *charger)
{
debugfs_remove(charger->debug_file);
}
#else
static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
{
return NULL;
}
static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger)
{
}
#endif
static inline void da9030_read_adc(struct da9030_charger *charger,
struct da9030_adc_res *adc)
{
da903x_reads(charger->master, DA9030_VBAT_RES,
sizeof(*adc), (uint8_t *)adc);
}
static void da9030_charger_update_state(struct da9030_charger *charger)
{
uint8_t val;
da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val);
charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0;
charger->mA = ((val >> 3) & 0xf) * 100;
charger->mV = (val & 0x7) * 50 + 4000;
da9030_read_adc(charger, &charger->adc);
da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault);
charger->chdet = da903x_query_status(charger->master,
DA9030_STATUS_CHDET);
}
static void da9030_set_charge(struct da9030_charger *charger, int on)
{
uint8_t val;
if (on) {
val = DA9030_CHRG_CHARGER_ENABLE;
val |= (charger->charge_milliamp / 100) << 3;
val |= (charger->charge_millivolt - 4000) / 50;
charger->is_on = 1;
} else {
val = 0;
charger->is_on = 0;
}
da903x_write(charger->master, DA9030_CHARGE_CONTROL, val);
}
static void da9030_charger_check_state(struct da9030_charger *charger)
{
da9030_charger_update_state(charger);
/* we wake or boot with external power on */
if (!charger->is_on) {
if ((charger->chdet) &&
(charger->adc.vbat_res <
charger->thresholds.vbat_charge_start)) {
da9030_set_charge(charger, 1);
}
} else {
if (charger->adc.vbat_res >=
charger->thresholds.vbat_charge_stop) {
da9030_set_charge(charger, 0);
da903x_write(charger->master, DA9030_VBATMON,
charger->thresholds.vbat_charge_restart);
} else if (charger->adc.vbat_res >
charger->thresholds.vbat_low) {
/* we are charging and passed LOW_THRESH,
so upate DA9030 VBAT threshold
*/
da903x_write(charger->master, DA9030_VBATMON,
charger->thresholds.vbat_low);
}
if (charger->adc.vchmax_res > charger->thresholds.vcharge_max ||
charger->adc.vchmin_res < charger->thresholds.vcharge_min ||
/* Tempreture readings are negative */
charger->adc.tbat_res < charger->thresholds.tbat_high ||
charger->adc.tbat_res > charger->thresholds.tbat_low) {
/* disable charger */
da9030_set_charge(charger, 0);
}
}
}
static void da9030_charging_monitor(struct work_struct *work)
{
struct da9030_charger *charger;
charger = container_of(work, struct da9030_charger, work.work);
da9030_charger_check_state(charger);
/* reschedule for the next time */
schedule_delayed_work(&charger->work, charger->interval);
}
static enum power_supply_property da9030_battery_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
};
static void da9030_battery_check_status(struct da9030_charger *charger,
union power_supply_propval *val)
{
if (charger->chdet) {
if (charger->is_on)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
} else {
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
}
}
static void da9030_battery_check_health(struct da9030_charger *charger,
union power_supply_propval *val)
{
if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER)
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else
val->intval = POWER_SUPPLY_HEALTH_GOOD;
}
static int da9030_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct da9030_charger *charger;
charger = container_of(psy, struct da9030_charger, psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
da9030_battery_check_status(charger, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
da9030_battery_check_health(charger, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = charger->battery_info->technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = charger->battery_info->voltage_max_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = charger->battery_info->voltage_min_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
val->intval =
da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = charger->battery_info->name;
break;
default:
break;
}
return 0;
}
static void da9030_battery_vbat_event(struct da9030_charger *charger)
{
da9030_read_adc(charger, &charger->adc);
if (charger->is_on)
return;
if (charger->adc.vbat_res < charger->thresholds.vbat_low) {
/* set VBAT threshold for critical */
da903x_write(charger->master, DA9030_VBATMON,
charger->thresholds.vbat_crit);
if (charger->battery_low)
charger->battery_low();
} else if (charger->adc.vbat_res <
charger->thresholds.vbat_crit) {
/* notify the system of battery critical */
if (charger->battery_critical)
charger->battery_critical();
}
}
static int da9030_battery_event(struct notifier_block *nb, unsigned long event,
void *data)
{
struct da9030_charger *charger =
container_of(nb, struct da9030_charger, nb);
int status;
switch (event) {
case DA9030_EVENT_CHDET:
status = da903x_query_status(charger->master,
DA9030_STATUS_CHDET);
da9030_set_charge(charger, status);
break;
case DA9030_EVENT_VBATMON:
da9030_battery_vbat_event(charger);
break;
case DA9030_EVENT_CHIOVER:
case DA9030_EVENT_TBAT:
da9030_set_charge(charger, 0);
break;
}
return 0;
}
static void da9030_battery_convert_thresholds(struct da9030_charger *charger,
struct da9030_battery_info *pdata)
{
charger->thresholds.tbat_low = pdata->tbat_low;
charger->thresholds.tbat_high = pdata->tbat_high;
charger->thresholds.tbat_restart = pdata->tbat_restart;
charger->thresholds.vbat_low =
da9030_millivolt_to_reg(pdata->vbat_low);
charger->thresholds.vbat_crit =
da9030_millivolt_to_reg(pdata->vbat_crit);
charger->thresholds.vbat_charge_start =
da9030_millivolt_to_reg(pdata->vbat_charge_start);
charger->thresholds.vbat_charge_stop =
da9030_millivolt_to_reg(pdata->vbat_charge_stop);
charger->thresholds.vbat_charge_restart =
da9030_millivolt_to_reg(pdata->vbat_charge_restart);
charger->thresholds.vcharge_min =
da9030_millivolt_to_reg(pdata->vcharge_min);
charger->thresholds.vcharge_max =
da9030_millivolt_to_reg(pdata->vcharge_max);
}
static void da9030_battery_setup_psy(struct da9030_charger *charger)
{
struct power_supply *psy = &charger->psy;
struct power_supply_info *info = charger->battery_info;
psy->name = info->name;
psy->use_for_apm = info->use_for_apm;
psy->type = POWER_SUPPLY_TYPE_BATTERY;
psy->get_property = da9030_battery_get_property;
psy->properties = da9030_battery_props;
psy->num_properties = ARRAY_SIZE(da9030_battery_props);
};
static int da9030_battery_charger_init(struct da9030_charger *charger)
{
char v[5];
int ret;
v[0] = v[1] = charger->thresholds.vbat_low;
v[2] = charger->thresholds.tbat_high;
v[3] = charger->thresholds.tbat_restart;
v[4] = charger->thresholds.tbat_low;
ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v);
if (ret)
return ret;
/*
* Enable reference voltage supply for ADC from the LDO_INTERNAL
* regulator. Must be set before ADC measurements can be made.
*/
ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL,
DA9030_ADC_LDO_INT_ENABLE |
DA9030_ADC_TBATREF_ENABLE);
if (ret)
return ret;
/* enable auto ADC measuremnts */
return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL,
DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON |
DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE |
DA9030_ADC_VBAT_ENABLE |
DA9030_ADC_AUTO_SLEEP_ENABLE);
}
static int da9030_battery_probe(struct platform_device *pdev)
{
struct da9030_charger *charger;
struct da9030_battery_info *pdata = pdev->dev.platform_data;
int ret;
if (pdata == NULL)
return -EINVAL;
if (pdata->charge_milliamp >= 1500 ||
pdata->charge_millivolt < 4000 ||
pdata->charge_millivolt > 4350)
return -EINVAL;
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (charger == NULL)
return -ENOMEM;
charger->master = pdev->dev.parent;
/* 10 seconds between monotor runs unless platfrom defines other
interval */
charger->interval = msecs_to_jiffies(
(pdata->batmon_interval ? : 10) * 1000);
charger->charge_milliamp = pdata->charge_milliamp;
charger->charge_millivolt = pdata->charge_millivolt;
charger->battery_info = pdata->battery_info;
charger->battery_low = pdata->battery_low;
charger->battery_critical = pdata->battery_critical;
da9030_battery_convert_thresholds(charger, pdata);
ret = da9030_battery_charger_init(charger);
if (ret)
goto err_charger_init;
INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor);
schedule_delayed_work(&charger->work, charger->interval);
charger->nb.notifier_call = da9030_battery_event;
ret = da903x_register_notifier(charger->master, &charger->nb,
DA9030_EVENT_CHDET |
DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER |
DA9030_EVENT_TBAT);
if (ret)
goto err_notifier;
da9030_battery_setup_psy(charger);
ret = power_supply_register(&pdev->dev, &charger->psy);
if (ret)
goto err_ps_register;
charger->debug_file = da9030_bat_create_debugfs(charger);
platform_set_drvdata(pdev, charger);
return 0;
err_ps_register:
da903x_unregister_notifier(charger->master, &charger->nb,
DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
err_notifier:
cancel_delayed_work(&charger->work);
err_charger_init:
kfree(charger);
return ret;
}
static int da9030_battery_remove(struct platform_device *dev)
{
struct da9030_charger *charger = platform_get_drvdata(dev);
da9030_bat_remove_debugfs(charger);
da903x_unregister_notifier(charger->master, &charger->nb,
DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
cancel_delayed_work(&charger->work);
power_supply_unregister(&charger->psy);
kfree(charger);
return 0;
}
static struct platform_driver da903x_battery_driver = {
.driver = {
.name = "da903x-battery",
.owner = THIS_MODULE,
},
.probe = da9030_battery_probe,
.remove = da9030_battery_remove,
};
static int da903x_battery_init(void)
{
return platform_driver_register(&da903x_battery_driver);
}
static void da903x_battery_exit(void)
{
platform_driver_unregister(&da903x_battery_driver);
}
module_init(da903x_battery_init);
module_exit(da903x_battery_exit);
MODULE_DESCRIPTION("DA9030 battery charger driver");
MODULE_AUTHOR("Mike Rapoport, CompuLab");
MODULE_LICENSE("GPL");

View file

@ -45,7 +45,7 @@ static ssize_t power_supply_show_property(struct device *dev,
};
static char *health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
"Unspecified failure"
"Unspecified failure", "Cold",
};
static char *technology_text[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",

View file

@ -0,0 +1,532 @@
/*
* Battery driver for wm8350 PMIC
*
* Copyright 2007, 2008 Wolfson Microelectronics PLC.
*
* Based on OLPC Battery Driver
*
* Copyright 2006 David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/wm8350/supply.h>
#include <linux/mfd/wm8350/core.h>
#include <linux/mfd/wm8350/comparator.h>
static int wm8350_read_battery_uvolts(struct wm8350 *wm8350)
{
return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0)
* WM8350_AUX_COEFF;
}
static int wm8350_read_line_uvolts(struct wm8350 *wm8350)
{
return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0)
* WM8350_AUX_COEFF;
}
static int wm8350_read_usb_uvolts(struct wm8350 *wm8350)
{
return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0)
* WM8350_AUX_COEFF;
}
#define WM8350_BATT_SUPPLY 1
#define WM8350_USB_SUPPLY 2
#define WM8350_LINE_SUPPLY 4
static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min)
{
if (!wm8350->power.rev_g_coeff)
return (((min - 30) / 15) & 0xf) << 8;
else
return (((min - 30) / 30) & 0xf) << 8;
}
static int wm8350_get_supplies(struct wm8350 *wm8350)
{
u16 sm, ov, co, chrg;
int supplies = 0;
sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS);
ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES);
co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES);
chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
/* USB_SM */
sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT;
/* CHG_ISEL */
chrg &= WM8350_CHG_ISEL_MASK;
/* If the USB state machine is active then we're using that with or
* without battery, otherwise check for wall supply */
if (((sm == WM8350_USB_SM_100_SLV) ||
(sm == WM8350_USB_SM_500_SLV) ||
(sm == WM8350_USB_SM_STDBY_SLV))
&& !(ov & WM8350_USB_LIMIT_OVRDE))
supplies = WM8350_USB_SUPPLY;
else if (((sm == WM8350_USB_SM_100_SLV) ||
(sm == WM8350_USB_SM_500_SLV) ||
(sm == WM8350_USB_SM_STDBY_SLV))
&& (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0))
supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY;
else if (co & WM8350_WALL_FB_OVRDE)
supplies = WM8350_LINE_SUPPLY;
else
supplies = WM8350_BATT_SUPPLY;
return supplies;
}
static int wm8350_charger_config(struct wm8350 *wm8350,
struct wm8350_charger_policy *policy)
{
u16 reg, eoc_mA, fast_limit_mA;
if (!policy) {
dev_warn(wm8350->dev,
"No charger policy, charger not configured.\n");
return -EINVAL;
}
/* make sure USB fast charge current is not > 500mA */
if (policy->fast_limit_USB_mA > 500) {
dev_err(wm8350->dev, "USB fast charge > 500mA\n");
return -EINVAL;
}
eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA);
wm8350_reg_unlock(wm8350);
reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1)
& WM8350_CHG_ENA_R168;
wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
reg | eoc_mA | policy->trickle_start_mV |
WM8350_CHG_TRICKLE_TEMP_CHOKE |
WM8350_CHG_TRICKLE_USB_CHOKE |
WM8350_CHG_FAST_USB_THROTTLE);
if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) {
fast_limit_mA =
WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA);
wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
policy->charge_mV | policy->trickle_charge_USB_mA |
fast_limit_mA | wm8350_charge_time_min(wm8350,
policy->charge_timeout));
} else {
fast_limit_mA =
WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA);
wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
policy->charge_mV | policy->trickle_charge_mA |
fast_limit_mA | wm8350_charge_time_min(wm8350,
policy->charge_timeout));
}
wm8350_reg_lock(wm8350);
return 0;
}
static int wm8350_batt_status(struct wm8350 *wm8350)
{
u16 state;
state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
state &= WM8350_CHG_STS_MASK;
switch (state) {
case WM8350_CHG_STS_OFF:
return POWER_SUPPLY_STATUS_DISCHARGING;
case WM8350_CHG_STS_TRICKLE:
case WM8350_CHG_STS_FAST:
return POWER_SUPPLY_STATUS_CHARGING;
default:
return POWER_SUPPLY_STATUS_UNKNOWN;
}
}
static ssize_t charger_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wm8350 *wm8350 = dev_get_drvdata(dev);
char *charge;
int state;
state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
WM8350_CHG_STS_MASK;
switch (state) {
case WM8350_CHG_STS_OFF:
charge = "Charger Off";
break;
case WM8350_CHG_STS_TRICKLE:
charge = "Trickle Charging";
break;
case WM8350_CHG_STS_FAST:
charge = "Fast Charging";
break;
default:
return 0;
}
return sprintf(buf, "%s\n", charge);
}
static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL);
static void wm8350_charger_handler(struct wm8350 *wm8350, int irq, void *data)
{
struct wm8350_power *power = &wm8350->power;
struct wm8350_charger_policy *policy = power->policy;
switch (irq) {
case WM8350_IRQ_CHG_BAT_FAIL:
dev_err(wm8350->dev, "battery failed\n");
break;
case WM8350_IRQ_CHG_TO:
dev_err(wm8350->dev, "charger timeout\n");
power_supply_changed(&power->battery);
break;
case WM8350_IRQ_CHG_BAT_HOT:
case WM8350_IRQ_CHG_BAT_COLD:
case WM8350_IRQ_CHG_START:
case WM8350_IRQ_CHG_END:
power_supply_changed(&power->battery);
break;
case WM8350_IRQ_CHG_FAST_RDY:
dev_dbg(wm8350->dev, "fast charger ready\n");
wm8350_charger_config(wm8350, policy);
wm8350_reg_unlock(wm8350);
wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
WM8350_CHG_FAST);
wm8350_reg_lock(wm8350);
break;
case WM8350_IRQ_CHG_VBATT_LT_3P9:
dev_warn(wm8350->dev, "battery < 3.9V\n");
break;
case WM8350_IRQ_CHG_VBATT_LT_3P1:
dev_warn(wm8350->dev, "battery < 3.1V\n");
break;
case WM8350_IRQ_CHG_VBATT_LT_2P85:
dev_warn(wm8350->dev, "battery < 2.85V\n");
break;
/* Supply change. We will overnotify but it should do
* no harm. */
case WM8350_IRQ_EXT_USB_FB:
case WM8350_IRQ_EXT_WALL_FB:
wm8350_charger_config(wm8350, policy);
case WM8350_IRQ_EXT_BAT_FB: /* Fall through */
power_supply_changed(&power->battery);
power_supply_changed(&power->usb);
power_supply_changed(&power->ac);
break;
default:
dev_err(wm8350->dev, "Unknown interrupt %d\n", irq);
}
}
/*********************************************************************
* AC Power
*********************************************************************/
static int wm8350_ac_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!(wm8350_get_supplies(wm8350) &
WM8350_LINE_SUPPLY);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = wm8350_read_line_uvolts(wm8350);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property wm8350_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
/*********************************************************************
* USB Power
*********************************************************************/
static int wm8350_usb_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!(wm8350_get_supplies(wm8350) &
WM8350_USB_SUPPLY);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = wm8350_read_usb_uvolts(wm8350);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property wm8350_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
/*********************************************************************
* Battery properties
*********************************************************************/
static int wm8350_bat_check_health(struct wm8350 *wm8350)
{
u16 reg;
if (wm8350_read_battery_uvolts(wm8350) < 2850000)
return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES);
if (reg & WM8350_CHG_BATT_HOT_OVRDE)
return POWER_SUPPLY_HEALTH_OVERHEAT;
if (reg & WM8350_CHG_BATT_COLD_OVRDE)
return POWER_SUPPLY_HEALTH_COLD;
return POWER_SUPPLY_HEALTH_GOOD;
}
static int wm8350_bat_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = wm8350_batt_status(wm8350);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!(wm8350_get_supplies(wm8350) &
WM8350_BATT_SUPPLY);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = wm8350_read_battery_uvolts(wm8350);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = wm8350_bat_check_health(wm8350);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property wm8350_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_HEALTH,
};
/*********************************************************************
* Initialisation
*********************************************************************/
static void wm8350_init_charger(struct wm8350 *wm8350)
{
/* register our interest in charger events */
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_TO);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_END);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_START);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1);
wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85);
/* and supply change events */
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_USB_FB);
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_WALL_FB);
wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
wm8350_charger_handler, NULL);
wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_BAT_FB);
}
static void free_charger_irq(struct wm8350 *wm8350)
{
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_TO);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_END);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_START);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1);
wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85);
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85);
wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_USB_FB);
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB);
wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_WALL_FB);
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB);
wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_BAT_FB);
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB);
}
static __devinit int wm8350_power_probe(struct platform_device *pdev)
{
struct wm8350 *wm8350 = platform_get_drvdata(pdev);
struct wm8350_power *power = &wm8350->power;
struct wm8350_charger_policy *policy = power->policy;
struct power_supply *usb = &power->usb;
struct power_supply *battery = &power->battery;
struct power_supply *ac = &power->ac;
int ret;
ac->name = "wm8350-ac";
ac->type = POWER_SUPPLY_TYPE_MAINS;
ac->properties = wm8350_ac_props;
ac->num_properties = ARRAY_SIZE(wm8350_ac_props);
ac->get_property = wm8350_ac_get_prop;
ret = power_supply_register(&pdev->dev, ac);
if (ret)
return ret;
battery->name = "wm8350-battery";
battery->properties = wm8350_bat_props;
battery->num_properties = ARRAY_SIZE(wm8350_bat_props);
battery->get_property = wm8350_bat_get_property;
battery->use_for_apm = 1;
ret = power_supply_register(&pdev->dev, battery);
if (ret)
goto battery_failed;
usb->name = "wm8350-usb",
usb->type = POWER_SUPPLY_TYPE_USB;
usb->properties = wm8350_usb_props;
usb->num_properties = ARRAY_SIZE(wm8350_usb_props);
usb->get_property = wm8350_usb_get_prop;
ret = power_supply_register(&pdev->dev, usb);
if (ret)
goto usb_failed;
ret = device_create_file(&pdev->dev, &dev_attr_charger_state);
if (ret < 0)
dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret);
ret = 0;
wm8350_init_charger(wm8350);
if (wm8350_charger_config(wm8350, policy) == 0) {
wm8350_reg_unlock(wm8350);
wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA);
wm8350_reg_lock(wm8350);
}
return ret;
usb_failed:
power_supply_unregister(battery);
battery_failed:
power_supply_unregister(ac);
return ret;
}
static __devexit int wm8350_power_remove(struct platform_device *pdev)
{
struct wm8350 *wm8350 = platform_get_drvdata(pdev);
struct wm8350_power *power = &wm8350->power;
free_charger_irq(wm8350);
device_remove_file(&pdev->dev, &dev_attr_charger_state);
power_supply_unregister(&power->battery);
power_supply_unregister(&power->ac);
power_supply_unregister(&power->usb);
return 0;
}
static struct platform_driver wm8350_power_driver = {
.probe = wm8350_power_probe,
.remove = __devexit_p(wm8350_power_remove),
.driver = {
.name = "wm8350-power",
},
};
static int __init wm8350_power_init(void)
{
return platform_driver_register(&wm8350_power_driver);
}
module_init(wm8350_power_init);
static void __exit wm8350_power_exit(void)
{
platform_driver_unregister(&wm8350_power_driver);
}
module_exit(wm8350_power_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Power supply driver for WM8350");
MODULE_ALIAS("platform:wm8350-power");

View file

@ -1380,6 +1380,13 @@ int wm8350_register_regulator(struct wm8350 *wm8350, int reg,
if (wm8350->pmic.pdev[reg])
return -EBUSY;
if (reg >= WM8350_DCDC_1 && reg <= WM8350_DCDC_6 &&
reg > wm8350->pmic.max_dcdc)
return -ENODEV;
if (reg >= WM8350_ISINK_A && reg <= WM8350_ISINK_B &&
reg > wm8350->pmic.max_isink)
return -ENODEV;
pdev = platform_device_alloc("wm8350-regulator", reg);
if (!pdev)
return -ENOMEM;

View file

@ -0,0 +1,79 @@
/*
* dm355evm_msp.h - support MSP430 microcontroller on DM355EVM board
*/
#ifndef __LINUX_I2C_DM355EVM_MSP
#define __LINUX_I2C_DM355EVM_MSP
/*
* Written against Spectrum's writeup for the A4 firmware revision,
* and tweaked to match source and rev D2 schematics by removing CPLD
* and NOR flash hooks (which were last appropriate in rev B boards).
*
* Note that the firmware supports a flavor of write posting ... to be
* sure a write completes, issue another read or write.
*/
/* utilities to access "registers" emulated by msp430 firmware */
extern int dm355evm_msp_write(u8 value, u8 reg);
extern int dm355evm_msp_read(u8 reg);
/* command/control registers */
#define DM355EVM_MSP_COMMAND 0x00
# define MSP_COMMAND_NULL 0
# define MSP_COMMAND_RESET_COLD 1
# define MSP_COMMAND_RESET_WARM 2
# define MSP_COMMAND_RESET_WARM_I 3
# define MSP_COMMAND_POWEROFF 4
# define MSP_COMMAND_IR_REINIT 5
#define DM355EVM_MSP_STATUS 0x01
# define MSP_STATUS_BAD_OFFSET BIT(0)
# define MSP_STATUS_BAD_COMMAND BIT(1)
# define MSP_STATUS_POWER_ERROR BIT(2)
# define MSP_STATUS_RXBUF_OVERRUN BIT(3)
#define DM355EVM_MSP_RESET 0x02 /* 0 bits == in reset */
# define MSP_RESET_DC5 BIT(0)
# define MSP_RESET_TVP5154 BIT(2)
# define MSP_RESET_IMAGER BIT(3)
# define MSP_RESET_ETHERNET BIT(4)
# define MSP_RESET_SYS BIT(5)
# define MSP_RESET_AIC33 BIT(7)
/* GPIO registers ... bit patterns mostly match the source MSP ports */
#define DM355EVM_MSP_LED 0x03 /* active low (MSP P4) */
#define DM355EVM_MSP_SWITCH1 0x04 /* (MSP P5, masked) */
# define MSP_SWITCH1_SW6_1 BIT(0)
# define MSP_SWITCH1_SW6_2 BIT(1)
# define MSP_SWITCH1_SW6_3 BIT(2)
# define MSP_SWITCH1_SW6_4 BIT(3)
# define MSP_SWITCH1_J1 BIT(4) /* NTSC/PAL */
# define MSP_SWITCH1_MSP_INT BIT(5) /* active low */
#define DM355EVM_MSP_SWITCH2 0x05 /* (MSP P6, masked) */
# define MSP_SWITCH2_SW10 BIT(3)
# define MSP_SWITCH2_SW11 BIT(4)
# define MSP_SWITCH2_SW12 BIT(5)
# define MSP_SWITCH2_SW13 BIT(6)
# define MSP_SWITCH2_SW14 BIT(7)
#define DM355EVM_MSP_SDMMC 0x06 /* (MSP P2, masked) */
# define MSP_SDMMC_0_WP BIT(1)
# define MSP_SDMMC_0_CD BIT(2) /* active low */
# define MSP_SDMMC_1_WP BIT(3)
# define MSP_SDMMC_1_CD BIT(4) /* active low */
#define DM355EVM_MSP_FIRMREV 0x07 /* not a GPIO (out of order) */
#define DM355EVM_MSP_VIDEO_IN 0x08 /* (MSP P3, masked) */
# define MSP_VIDEO_IMAGER BIT(7) /* low == tvp5146 */
/* power supply registers are currently omitted */
/* RTC registers */
#define DM355EVM_MSP_RTC_0 0x12 /* LSB */
#define DM355EVM_MSP_RTC_1 0x13
#define DM355EVM_MSP_RTC_2 0x14
#define DM355EVM_MSP_RTC_3 0x15 /* MSB */
/* input event queue registers; code == ((HIGH << 8) | LOW) */
#define DM355EVM_MSP_INPUT_COUNT 0x16 /* decrement by reading LOW */
#define DM355EVM_MSP_INPUT_HIGH 0x17
#define DM355EVM_MSP_INPUT_LOW 0x18
#endif /* __LINUX_I2C_DM355EVM_MSP */

View file

@ -78,8 +78,8 @@ int twl4030_i2c_read_u8(u8 mod_no, u8 *val, u8 reg);
* IMPORTANT: For twl4030_i2c_write(), allocate num_bytes + 1
* for the value, and populate your data starting at offset 1.
*/
int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, u8 num_bytes);
int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, u8 num_bytes);
int twl4030_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes);
int twl4030_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes);
/*----------------------------------------------------------------------*/
@ -278,6 +278,18 @@ struct twl4030_platform_data {
struct twl4030_keypad_data *keypad;
struct twl4030_usb_data *usb;
/* LDO regulators */
struct regulator_init_data *vdac;
struct regulator_init_data *vpll1;
struct regulator_init_data *vpll2;
struct regulator_init_data *vmmc1;
struct regulator_init_data *vmmc2;
struct regulator_init_data *vsim;
struct regulator_init_data *vaux1;
struct regulator_init_data *vaux2;
struct regulator_init_data *vaux3;
struct regulator_init_data *vaux4;
/* REVISIT more to come ... _nothing_ should be hard-wired */
};
@ -285,33 +297,6 @@ struct twl4030_platform_data {
int twl4030_sih_setup(int module);
/*
* FIXME completely stop using TWL4030_IRQ_BASE ... instead, pass the
* IRQ data to subsidiary devices using platform device resources.
*/
/* IRQ information-need base */
#include <mach/irqs.h>
/* TWL4030 interrupts */
/* #define TWL4030_MODIRQ_GPIO (TWL4030_IRQ_BASE + 0) */
#define TWL4030_MODIRQ_KEYPAD (TWL4030_IRQ_BASE + 1)
#define TWL4030_MODIRQ_BCI (TWL4030_IRQ_BASE + 2)
#define TWL4030_MODIRQ_MADC (TWL4030_IRQ_BASE + 3)
/* #define TWL4030_MODIRQ_USB (TWL4030_IRQ_BASE + 4) */
/* #define TWL4030_MODIRQ_PWR (TWL4030_IRQ_BASE + 5) */
#define TWL4030_PWRIRQ_PWRBTN (TWL4030_PWR_IRQ_BASE + 0)
/* #define TWL4030_PWRIRQ_CHG_PRES (TWL4030_PWR_IRQ_BASE + 1) */
/* #define TWL4030_PWRIRQ_USB_PRES (TWL4030_PWR_IRQ_BASE + 2) */
/* #define TWL4030_PWRIRQ_RTC (TWL4030_PWR_IRQ_BASE + 3) */
/* #define TWL4030_PWRIRQ_HOT_DIE (TWL4030_PWR_IRQ_BASE + 4) */
/* #define TWL4030_PWRIRQ_PWROK_TIMEOUT (TWL4030_PWR_IRQ_BASE + 5) */
/* #define TWL4030_PWRIRQ_MBCHG (TWL4030_PWR_IRQ_BASE + 6) */
/* #define TWL4030_PWRIRQ_SC_DETECT (TWL4030_PWR_IRQ_BASE + 7) */
/* Rest are unsued currently*/
/* Offsets to Power Registers */
#define TWL4030_VDAC_DEV_GRP 0x3B
#define TWL4030_VDAC_DEDICATED 0x3E
@ -322,10 +307,6 @@ int twl4030_sih_setup(int module);
#define TWL4030_VAUX3_DEV_GRP 0x1F
#define TWL4030_VAUX3_DEDICATED 0x22
/* TWL4030 GPIO interrupt definitions */
#define TWL4030_GPIO_IRQ_NO(n) (TWL4030_GPIO_IRQ_BASE + (n))
/*
* Exported TWL4030 GPIO APIs
*
@ -340,4 +321,38 @@ int twl4030_set_gpio_debounce(int gpio, int enable);
static inline int twl4030charger_usb_en(int enable) { return 0; }
#endif
/*----------------------------------------------------------------------*/
/* Linux-specific regulator identifiers ... for now, we only support
* the LDOs, and leave the three buck converters alone. VDD1 and VDD2
* need to tie into hardware based voltage scaling (cpufreq etc), while
* VIO is generally fixed.
*/
/* EXTERNAL dc-to-dc buck converters */
#define TWL4030_REG_VDD1 0
#define TWL4030_REG_VDD2 1
#define TWL4030_REG_VIO 2
/* EXTERNAL LDOs */
#define TWL4030_REG_VDAC 3
#define TWL4030_REG_VPLL1 4
#define TWL4030_REG_VPLL2 5 /* not on all chips */
#define TWL4030_REG_VMMC1 6
#define TWL4030_REG_VMMC2 7 /* not on all chips */
#define TWL4030_REG_VSIM 8 /* not on all chips */
#define TWL4030_REG_VAUX1 9 /* not on all chips */
#define TWL4030_REG_VAUX2_4030 10 /* (twl4030-specific) */
#define TWL4030_REG_VAUX2 11 /* (twl5030 and newer) */
#define TWL4030_REG_VAUX3 12 /* not on all chips */
#define TWL4030_REG_VAUX4 13 /* not on all chips */
/* INTERNAL LDOs */
#define TWL4030_REG_VINTANA1 14
#define TWL4030_REG_VINTANA2 15
#define TWL4030_REG_VINTDIG 16
#define TWL4030_REG_VUSB1V5 17
#define TWL4030_REG_VUSB1V8 18
#define TWL4030_REG_VUSB3V1 19
#endif /* End of __TWL4030_H */

View file

@ -32,6 +32,7 @@ enum {
DA9030_ID_LDO18,
DA9030_ID_LDO19,
DA9030_ID_LDO_INT, /* LDO Internal */
DA9030_ID_BAT, /* battery charger */
DA9034_ID_LED_1,
DA9034_ID_LED_2,
@ -93,6 +94,43 @@ struct da9034_touch_pdata {
int y_inverted;
};
/* DA9030 battery charger data */
struct power_supply_info;
struct da9030_battery_info {
/* battery parameters */
struct power_supply_info *battery_info;
/* current and voltage to use for battery charging */
unsigned int charge_milliamp;
unsigned int charge_millivolt;
/* voltage thresholds (in millivolts) */
int vbat_low;
int vbat_crit;
int vbat_charge_start;
int vbat_charge_stop;
int vbat_charge_restart;
/* battery nominal minimal and maximal voltages in millivolts */
int vcharge_min;
int vcharge_max;
/* Temperature thresholds. These are DA9030 register values
"as is" and should be measured for each battery type */
int tbat_low;
int tbat_high;
int tbat_restart;
/* battery monitor interval (seconds) */
unsigned int batmon_interval;
/* platform callbacks for battery low and critical events */
void (*battery_low)(void);
void (*battery_critical)(void);
};
struct da903x_subdev_info {
int id;
const char *name;
@ -190,11 +228,13 @@ extern int da903x_unregister_notifier(struct device *dev,
extern int da903x_query_status(struct device *dev, unsigned int status);
/* NOTE: the two functions below are not intended for use outside
* of the DA9034 sub-device drivers
/* NOTE: the functions below are not intended for use outside
* of the DA903x sub-device drivers
*/
extern int da903x_write(struct device *dev, int reg, uint8_t val);
extern int da903x_writes(struct device *dev, int reg, int len, uint8_t *val);
extern int da903x_read(struct device *dev, int reg, uint8_t *val);
extern int da903x_reads(struct device *dev, int reg, int len, uint8_t *val);
extern int da903x_update(struct device *dev, int reg, uint8_t val, uint8_t mask);
extern int da903x_set_bits(struct device *dev, int reg, uint8_t bit_mask);
extern int da903x_clr_bits(struct device *dev, int reg, uint8_t bit_mask);

View file

@ -164,4 +164,12 @@
#define WM8350_AUXADC_BATT 6
#define WM8350_AUXADC_TEMP 7
struct wm8350;
/*
* AUX ADC Readback
*/
int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale,
int vref);
#endif

View file

@ -29,6 +29,7 @@
*/
#define WM8350_RESET_ID 0x00
#define WM8350_ID 0x01
#define WM8350_REVISION 0x02
#define WM8350_SYSTEM_CONTROL_1 0x03
#define WM8350_SYSTEM_CONTROL_2 0x04
#define WM8350_SYSTEM_HIBERNATE 0x05
@ -57,6 +58,10 @@
#define WM8350_OVER_CURRENT_INT_STATUS_MASK 0x25
#define WM8350_GPIO_INT_STATUS_MASK 0x26
#define WM8350_COMPARATOR_INT_STATUS_MASK 0x27
#define WM8350_CHARGER_OVERRIDES 0xE2
#define WM8350_MISC_OVERRIDES 0xE3
#define WM8350_COMPARATOR_OVERRIDES 0xE7
#define WM8350_STATE_MACHINE_STATUS 0xE9
#define WM8350_MAX_REGISTER 0xFF
@ -76,6 +81,11 @@
#define WM8350_CONF_STS_MASK 0x0C00
#define WM8350_CUST_ID_MASK 0x00FF
/*
* R2 (0x02) - Revision
*/
#define WM8350_MASK_REV_MASK 0x00FF
/*
* R3 (0x03) - System Control 1
*/
@ -523,6 +533,35 @@
#define WM8350_DC2_STS 0x0002
#define WM8350_DC1_STS 0x0001
/*
* R226 (0xE2) - Charger status
*/
#define WM8350_CHG_BATT_HOT_OVRDE 0x8000
#define WM8350_CHG_BATT_COLD_OVRDE 0x4000
/*
* R227 (0xE3) - Misc Overrides
*/
#define WM8350_USB_LIMIT_OVRDE 0x0400
/*
* R227 (0xE7) - Comparator Overrides
*/
#define WM8350_USB_FB_OVRDE 0x8000
#define WM8350_WALL_FB_OVRDE 0x4000
#define WM8350_BATT_FB_OVRDE 0x2000
/*
* R233 (0xE9) - State Machinine Status
*/
#define WM8350_USB_SM_MASK 0x0700
#define WM8350_USB_SM_SHIFT 8
#define WM8350_USB_SM_100_SLV 1
#define WM8350_USB_SM_500_SLV 5
#define WM8350_USB_SM_STDBY_SLV 7
/* WM8350 wake up conditions */
#define WM8350_IRQ_WKUP_OFF_STATE 43
#define WM8350_IRQ_WKUP_HIB_STATE 44
@ -536,6 +575,7 @@
#define WM8350_REV_E 0x4
#define WM8350_REV_F 0x5
#define WM8350_REV_G 0x6
#define WM8350_REV_H 0x7
#define WM8350_NUM_IRQ 63
@ -549,6 +589,14 @@ extern const u16 wm8350_mode0_defaults[];
extern const u16 wm8350_mode1_defaults[];
extern const u16 wm8350_mode2_defaults[];
extern const u16 wm8350_mode3_defaults[];
extern const u16 wm8351_mode0_defaults[];
extern const u16 wm8351_mode1_defaults[];
extern const u16 wm8351_mode2_defaults[];
extern const u16 wm8351_mode3_defaults[];
extern const u16 wm8352_mode0_defaults[];
extern const u16 wm8352_mode1_defaults[];
extern const u16 wm8352_mode2_defaults[];
extern const u16 wm8352_mode3_defaults[];
struct wm8350;
@ -558,8 +606,6 @@ struct wm8350_irq {
};
struct wm8350 {
int rev; /* chip revision */
struct device *dev;
/* device IO */
@ -572,6 +618,8 @@ struct wm8350 {
void *src);
u16 *reg_cache;
struct mutex auxadc_mutex;
/* Interrupt handling */
struct work_struct irq_work;
struct mutex irq_mutex; /* IRQ table mutex */

View file

@ -701,6 +701,10 @@ struct platform_device;
struct regulator_init_data;
struct wm8350_pmic {
/* Number of regulators of each type on this device */
int max_dcdc;
int max_isink;
/* ISINK to DCDC mapping */
int isink_A_dcdc;
int isink_B_dcdc;

View file

@ -13,7 +13,8 @@
#ifndef __LINUX_MFD_WM8350_SUPPLY_H_
#define __LINUX_MFD_WM8350_SUPPLY_H_
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/power_supply.h>
/*
* Charger registers
@ -104,8 +105,30 @@
#define WM8350_IRQ_EXT_WALL_FB 37
#define WM8350_IRQ_EXT_BAT_FB 38
/*
* Policy to control charger state machine.
*/
struct wm8350_charger_policy {
/* charger state machine policy - set in machine driver */
int eoc_mA; /* end of charge current (mA) */
int charge_mV; /* charge voltage */
int fast_limit_mA; /* fast charge current limit */
int fast_limit_USB_mA; /* USB fast charge current limit */
int charge_timeout; /* charge timeout (mins) */
int trickle_start_mV; /* trickle charge starts at mV */
int trickle_charge_mA; /* trickle charge current */
int trickle_charge_USB_mA; /* USB trickle charge current */
};
struct wm8350_power {
struct platform_device *pdev;
struct power_supply battery;
struct power_supply usb;
struct power_supply ac;
struct wm8350_charger_policy *policy;
int rev_g_coeff;
};
#endif

View file

@ -45,6 +45,7 @@ enum {
POWER_SUPPLY_HEALTH_DEAD,
POWER_SUPPLY_HEALTH_OVERVOLTAGE,
POWER_SUPPLY_HEALTH_UNSPEC_FAILURE,
POWER_SUPPLY_HEALTH_COLD,
};
enum {