Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6: (66 commits) mfd: Fix ucb1x00 build failure for collie_defconfig mfd: Fix lpc_sch related depends/selects, fix build error gpio: Fix sch_gpio warning gpio: add Intel SCH GPIO controller driver i2c: convert i2c-isch to platform_device mfd: Use completion interrupt for WM831x AUXADC mfd: Use completion interrupt for WM835x AUXADC mfd: Introduce remove_script function for twl4030 mfd/mmc: SDHI Kconfig update mfd: sh_mobile_sdhi MMC_CAP_MMC_HIGHSPEED support gpiolib: Force wm831x GPIOs into GPIO mode when requested mfd: Add WM831x revision B support gpiolib: Correct debugfs display of WM831x GPIO inversion gpiolib: Actually set output state in wm831x_gpio_direction_output() tmio_mmc: Balance cell enable()/disable() calls tmio_mmc: Remove const from platform data V3 tmio_mmc: Use 100ms mmc_detect_change() delay tmio_mmc: Add MMC_CAP_MMC_HIGHSPEED support V2 tmio_mmc: Keep card-detect interrupts enabled mfd: Add twl6030 base addr for ID0, ID1, ID2 ...
This commit is contained in:
commit
6dc3eb5c1f
|
@ -94,6 +94,23 @@ config GPIO_VR41XX
|
|||
help
|
||||
Say yes here to support the NEC VR4100 series General-purpose I/O Uint
|
||||
|
||||
config GPIO_SCH
|
||||
tristate "Intel SCH GPIO"
|
||||
depends on GPIOLIB && PCI
|
||||
select MFD_CORE
|
||||
select LPC_SCH
|
||||
help
|
||||
Say yes here to support GPIO interface on Intel Poulsbo SCH.
|
||||
The Intel SCH contains a total of 14 GPIO pins. Ten GPIOs are
|
||||
powered by the core power rail and are turned off during sleep
|
||||
modes (S3 and higher). The remaining four GPIOs are powered by
|
||||
the Intel SCH suspend power supply. These GPIOs remain
|
||||
active during S3. The suspend powered GPIOs can be used to wake the
|
||||
system from the Suspend-to-RAM state.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sch-gpio.
|
||||
|
||||
comment "I2C GPIO expanders:"
|
||||
|
||||
config GPIO_MAX7300
|
||||
|
@ -185,6 +202,20 @@ config GPIO_WM831X
|
|||
Say yes here to access the GPIO signals of WM831x power management
|
||||
chips from Wolfson Microelectronics.
|
||||
|
||||
config GPIO_WM8350
|
||||
tristate "WM8350 GPIOs"
|
||||
depends on MFD_WM8350
|
||||
help
|
||||
Say yes here to access the GPIO signals of WM8350 power management
|
||||
chips from Wolfson Microelectronics.
|
||||
|
||||
config GPIO_WM8994
|
||||
tristate "WM8994 GPIOs"
|
||||
depends on MFD_WM8994
|
||||
help
|
||||
Say yes here to access the GPIO signals of WM8994 audio hub
|
||||
CODECs from Wolfson Microelectronics.
|
||||
|
||||
config GPIO_ADP5520
|
||||
tristate "GPIO Support for ADP5520 PMIC"
|
||||
depends on PMIC_ADP5520
|
||||
|
|
|
@ -25,3 +25,6 @@ obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o
|
|||
obj-$(CONFIG_GPIO_IT8761E) += it8761e_gpio.o
|
||||
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
|
||||
obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
|
||||
obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o
|
||||
obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
|
||||
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* sch_gpio.c - GPIO interface for Intel Poulsbo SCH
|
||||
*
|
||||
* Copyright (c) 2010 CompuLab Ltd
|
||||
* Author: Denis Turischev <denis@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 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
static DEFINE_SPINLOCK(gpio_lock);
|
||||
|
||||
#define CGEN (0x00)
|
||||
#define CGIO (0x04)
|
||||
#define CGLV (0x08)
|
||||
|
||||
#define RGEN (0x20)
|
||||
#define RGIO (0x24)
|
||||
#define RGLV (0x28)
|
||||
|
||||
static unsigned short gpio_ba;
|
||||
|
||||
static int sch_gpio_core_direction_in(struct gpio_chip *gc, unsigned gpio_num)
|
||||
{
|
||||
u8 curr_dirs;
|
||||
unsigned short offset, bit;
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
offset = CGIO + gpio_num / 8;
|
||||
bit = gpio_num % 8;
|
||||
|
||||
curr_dirs = inb(gpio_ba + offset);
|
||||
|
||||
if (!(curr_dirs & (1 << bit)))
|
||||
outb(curr_dirs | (1 << bit), gpio_ba + offset);
|
||||
|
||||
spin_unlock(&gpio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sch_gpio_core_get(struct gpio_chip *gc, unsigned gpio_num)
|
||||
{
|
||||
int res;
|
||||
unsigned short offset, bit;
|
||||
|
||||
offset = CGLV + gpio_num / 8;
|
||||
bit = gpio_num % 8;
|
||||
|
||||
res = !!(inb(gpio_ba + offset) & (1 << bit));
|
||||
return res;
|
||||
}
|
||||
|
||||
static void sch_gpio_core_set(struct gpio_chip *gc, unsigned gpio_num, int val)
|
||||
{
|
||||
u8 curr_vals;
|
||||
unsigned short offset, bit;
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
offset = CGLV + gpio_num / 8;
|
||||
bit = gpio_num % 8;
|
||||
|
||||
curr_vals = inb(gpio_ba + offset);
|
||||
|
||||
if (val)
|
||||
outb(curr_vals | (1 << bit), gpio_ba + offset);
|
||||
else
|
||||
outb((curr_vals & ~(1 << bit)), gpio_ba + offset);
|
||||
spin_unlock(&gpio_lock);
|
||||
}
|
||||
|
||||
static int sch_gpio_core_direction_out(struct gpio_chip *gc,
|
||||
unsigned gpio_num, int val)
|
||||
{
|
||||
u8 curr_dirs;
|
||||
unsigned short offset, bit;
|
||||
|
||||
sch_gpio_core_set(gc, gpio_num, val);
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
offset = CGIO + gpio_num / 8;
|
||||
bit = gpio_num % 8;
|
||||
|
||||
curr_dirs = inb(gpio_ba + offset);
|
||||
if (curr_dirs & (1 << bit))
|
||||
outb(curr_dirs & ~(1 << bit), gpio_ba + offset);
|
||||
|
||||
spin_unlock(&gpio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gpio_chip sch_gpio_core = {
|
||||
.label = "sch_gpio_core",
|
||||
.owner = THIS_MODULE,
|
||||
.direction_input = sch_gpio_core_direction_in,
|
||||
.get = sch_gpio_core_get,
|
||||
.direction_output = sch_gpio_core_direction_out,
|
||||
.set = sch_gpio_core_set,
|
||||
};
|
||||
|
||||
static int sch_gpio_resume_direction_in(struct gpio_chip *gc,
|
||||
unsigned gpio_num)
|
||||
{
|
||||
u8 curr_dirs;
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
curr_dirs = inb(gpio_ba + RGIO);
|
||||
|
||||
if (!(curr_dirs & (1 << gpio_num)))
|
||||
outb(curr_dirs | (1 << gpio_num) , gpio_ba + RGIO);
|
||||
|
||||
spin_unlock(&gpio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sch_gpio_resume_get(struct gpio_chip *gc, unsigned gpio_num)
|
||||
{
|
||||
return !!(inb(gpio_ba + RGLV) & (1 << gpio_num));
|
||||
}
|
||||
|
||||
static void sch_gpio_resume_set(struct gpio_chip *gc,
|
||||
unsigned gpio_num, int val)
|
||||
{
|
||||
u8 curr_vals;
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
curr_vals = inb(gpio_ba + RGLV);
|
||||
|
||||
if (val)
|
||||
outb(curr_vals | (1 << gpio_num), gpio_ba + RGLV);
|
||||
else
|
||||
outb((curr_vals & ~(1 << gpio_num)), gpio_ba + RGLV);
|
||||
|
||||
spin_unlock(&gpio_lock);
|
||||
}
|
||||
|
||||
static int sch_gpio_resume_direction_out(struct gpio_chip *gc,
|
||||
unsigned gpio_num, int val)
|
||||
{
|
||||
u8 curr_dirs;
|
||||
|
||||
sch_gpio_resume_set(gc, gpio_num, val);
|
||||
|
||||
spin_lock(&gpio_lock);
|
||||
|
||||
curr_dirs = inb(gpio_ba + RGIO);
|
||||
if (curr_dirs & (1 << gpio_num))
|
||||
outb(curr_dirs & ~(1 << gpio_num), gpio_ba + RGIO);
|
||||
|
||||
spin_unlock(&gpio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gpio_chip sch_gpio_resume = {
|
||||
.label = "sch_gpio_resume",
|
||||
.owner = THIS_MODULE,
|
||||
.direction_input = sch_gpio_resume_direction_in,
|
||||
.get = sch_gpio_resume_get,
|
||||
.direction_output = sch_gpio_resume_direction_out,
|
||||
.set = sch_gpio_resume_set,
|
||||
};
|
||||
|
||||
static int __devinit sch_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int err;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
|
||||
if (!request_region(res->start, resource_size(res), pdev->name))
|
||||
return -EBUSY;
|
||||
|
||||
gpio_ba = res->start;
|
||||
|
||||
sch_gpio_core.base = 0;
|
||||
sch_gpio_core.ngpio = 10;
|
||||
sch_gpio_core.dev = &pdev->dev;
|
||||
|
||||
sch_gpio_resume.base = 10;
|
||||
sch_gpio_resume.ngpio = 4;
|
||||
sch_gpio_resume.dev = &pdev->dev;
|
||||
|
||||
err = gpiochip_add(&sch_gpio_core);
|
||||
if (err < 0)
|
||||
goto err_sch_gpio_core;
|
||||
|
||||
err = gpiochip_add(&sch_gpio_resume);
|
||||
if (err < 0)
|
||||
goto err_sch_gpio_resume;
|
||||
|
||||
/*
|
||||
* GPIO[6:0] enabled by default
|
||||
* GPIO7 is configured by the CMC as SLPIOVR
|
||||
* Enable GPIO[9:8] core powered gpios explicitly
|
||||
*/
|
||||
outb(0x3, gpio_ba + CGEN + 1);
|
||||
/*
|
||||
* SUS_GPIO[2:0] enabled by default
|
||||
* Enable SUS_GPIO3 resume powered gpio explicitly
|
||||
*/
|
||||
outb(0x8, gpio_ba + RGEN);
|
||||
|
||||
return 0;
|
||||
|
||||
err_sch_gpio_resume:
|
||||
err = gpiochip_remove(&sch_gpio_core);
|
||||
if (err)
|
||||
dev_err(&pdev->dev, "%s failed, %d\n",
|
||||
"gpiochip_remove()", err);
|
||||
|
||||
err_sch_gpio_core:
|
||||
release_region(res->start, resource_size(res));
|
||||
gpio_ba = 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit sch_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
if (gpio_ba) {
|
||||
int err;
|
||||
|
||||
err = gpiochip_remove(&sch_gpio_core);
|
||||
if (err)
|
||||
dev_err(&pdev->dev, "%s failed, %d\n",
|
||||
"gpiochip_remove()", err);
|
||||
err = gpiochip_remove(&sch_gpio_resume);
|
||||
if (err)
|
||||
dev_err(&pdev->dev, "%s failed, %d\n",
|
||||
"gpiochip_remove()", err);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
|
||||
release_region(res->start, resource_size(res));
|
||||
gpio_ba = 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sch_gpio_driver = {
|
||||
.driver = {
|
||||
.name = "sch_gpio",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sch_gpio_probe,
|
||||
.remove = __devexit_p(sch_gpio_remove),
|
||||
};
|
||||
|
||||
static int __init sch_gpio_init(void)
|
||||
{
|
||||
return platform_driver_register(&sch_gpio_driver);
|
||||
}
|
||||
|
||||
static void __exit sch_gpio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sch_gpio_driver);
|
||||
}
|
||||
|
||||
module_init(sch_gpio_init);
|
||||
module_exit(sch_gpio_exit);
|
||||
|
||||
MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
|
||||
MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:sch_gpio");
|
|
@ -38,10 +38,14 @@ static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
|
|||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
struct wm831x *wm831x = wm831x_gpio->wm831x;
|
||||
int val = WM831X_GPN_DIR;
|
||||
|
||||
if (wm831x->has_gpio_ena)
|
||||
val |= WM831X_GPN_TRI;
|
||||
|
||||
return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
|
||||
WM831X_GPN_DIR | WM831X_GPN_TRI,
|
||||
WM831X_GPN_DIR);
|
||||
WM831X_GPN_DIR | WM831X_GPN_TRI |
|
||||
WM831X_GPN_FN_MASK, val);
|
||||
}
|
||||
|
||||
static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset)
|
||||
|
@ -60,16 +64,6 @@ static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int wm831x_gpio_direction_out(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
struct wm831x *wm831x = wm831x_gpio->wm831x;
|
||||
|
||||
return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
|
||||
WM831X_GPN_DIR | WM831X_GPN_TRI, 0);
|
||||
}
|
||||
|
||||
static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
|
@ -79,6 +73,29 @@ static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
|||
value << offset);
|
||||
}
|
||||
|
||||
static int wm831x_gpio_direction_out(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
struct wm831x *wm831x = wm831x_gpio->wm831x;
|
||||
int val = 0;
|
||||
int ret;
|
||||
|
||||
if (wm831x->has_gpio_ena)
|
||||
val |= WM831X_GPN_TRI;
|
||||
|
||||
ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
|
||||
WM831X_GPN_DIR | WM831X_GPN_TRI |
|
||||
WM831X_GPN_FN_MASK, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Can only set GPIO state once it's in output mode */
|
||||
wm831x_gpio_set(chip, offset, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm831x_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
|
@ -95,7 +112,7 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
|||
{
|
||||
struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
|
||||
struct wm831x *wm831x = wm831x_gpio->wm831x;
|
||||
int i;
|
||||
int i, tristated;
|
||||
|
||||
for (i = 0; i < chip->ngpio; i++) {
|
||||
int gpio = i + chip->base;
|
||||
|
@ -162,15 +179,19 @@ static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
|||
break;
|
||||
}
|
||||
|
||||
tristated = reg & WM831X_GPN_TRI;
|
||||
if (wm831x->has_gpio_ena)
|
||||
tristated = !tristated;
|
||||
|
||||
seq_printf(s, " %s %s %s %s%s\n"
|
||||
" %s%s (0x%4x)\n",
|
||||
reg & WM831X_GPN_DIR ? "in" : "out",
|
||||
wm831x_gpio_get(chip, i) ? "high" : "low",
|
||||
pull,
|
||||
powerdomain,
|
||||
reg & WM831X_GPN_POL ? " inverted" : "",
|
||||
reg & WM831X_GPN_POL ? "" : " inverted",
|
||||
reg & WM831X_GPN_OD ? "open-drain" : "CMOS",
|
||||
reg & WM831X_GPN_TRI ? " tristated" : "",
|
||||
tristated ? " tristated" : "",
|
||||
reg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* wm835x-gpiolib.c -- gpiolib support for Wolfson WM835x PMICs
|
||||
*
|
||||
* 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/mfd/wm8350/core.h>
|
||||
#include <linux/mfd/wm8350/gpio.h>
|
||||
|
||||
struct wm8350_gpio_data {
|
||||
struct wm8350 *wm8350;
|
||||
struct gpio_chip gpio_chip;
|
||||
};
|
||||
|
||||
static inline struct wm8350_gpio_data *to_wm8350_gpio(struct gpio_chip *chip)
|
||||
{
|
||||
return container_of(chip, struct wm8350_gpio_data, gpio_chip);
|
||||
}
|
||||
|
||||
static int wm8350_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip);
|
||||
struct wm8350 *wm8350 = wm8350_gpio->wm8350;
|
||||
|
||||
return wm8350_set_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O,
|
||||
1 << offset);
|
||||
}
|
||||
|
||||
static int wm8350_gpio_get(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip);
|
||||
struct wm8350 *wm8350 = wm8350_gpio->wm8350;
|
||||
int ret;
|
||||
|
||||
ret = wm8350_reg_read(wm8350, WM8350_GPIO_LEVEL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret & (1 << offset))
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8350_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip);
|
||||
struct wm8350 *wm8350 = wm8350_gpio->wm8350;
|
||||
|
||||
if (value)
|
||||
wm8350_set_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset);
|
||||
else
|
||||
wm8350_clear_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset);
|
||||
}
|
||||
|
||||
static int wm8350_gpio_direction_out(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip);
|
||||
struct wm8350 *wm8350 = wm8350_gpio->wm8350;
|
||||
int ret;
|
||||
|
||||
ret = wm8350_clear_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O,
|
||||
1 << offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Don't have an atomic direction/value setup */
|
||||
wm8350_gpio_set(chip, offset, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8350_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = to_wm8350_gpio(chip);
|
||||
struct wm8350 *wm8350 = wm8350_gpio->wm8350;
|
||||
|
||||
if (!wm8350->irq_base)
|
||||
return -EINVAL;
|
||||
|
||||
return wm8350->irq_base + WM8350_IRQ_GPIO(offset);
|
||||
}
|
||||
|
||||
static struct gpio_chip template_chip = {
|
||||
.label = "wm8350",
|
||||
.owner = THIS_MODULE,
|
||||
.direction_input = wm8350_gpio_direction_in,
|
||||
.get = wm8350_gpio_get,
|
||||
.direction_output = wm8350_gpio_direction_out,
|
||||
.set = wm8350_gpio_set,
|
||||
.to_irq = wm8350_gpio_to_irq,
|
||||
.can_sleep = 1,
|
||||
};
|
||||
|
||||
static int __devinit wm8350_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8350 *wm8350 = dev_get_drvdata(pdev->dev.parent);
|
||||
struct wm8350_platform_data *pdata = wm8350->dev->platform_data;
|
||||
struct wm8350_gpio_data *wm8350_gpio;
|
||||
int ret;
|
||||
|
||||
wm8350_gpio = kzalloc(sizeof(*wm8350_gpio), GFP_KERNEL);
|
||||
if (wm8350_gpio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
wm8350_gpio->wm8350 = wm8350;
|
||||
wm8350_gpio->gpio_chip = template_chip;
|
||||
wm8350_gpio->gpio_chip.ngpio = 13;
|
||||
wm8350_gpio->gpio_chip.dev = &pdev->dev;
|
||||
if (pdata && pdata->gpio_base)
|
||||
wm8350_gpio->gpio_chip.base = pdata->gpio_base;
|
||||
else
|
||||
wm8350_gpio->gpio_chip.base = -1;
|
||||
|
||||
ret = gpiochip_add(&wm8350_gpio->gpio_chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, wm8350_gpio);
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
kfree(wm8350_gpio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit wm8350_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8350_gpio_data *wm8350_gpio = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = gpiochip_remove(&wm8350_gpio->gpio_chip);
|
||||
if (ret == 0)
|
||||
kfree(wm8350_gpio);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver wm8350_gpio_driver = {
|
||||
.driver.name = "wm8350-gpio",
|
||||
.driver.owner = THIS_MODULE,
|
||||
.probe = wm8350_gpio_probe,
|
||||
.remove = __devexit_p(wm8350_gpio_remove),
|
||||
};
|
||||
|
||||
static int __init wm8350_gpio_init(void)
|
||||
{
|
||||
return platform_driver_register(&wm8350_gpio_driver);
|
||||
}
|
||||
subsys_initcall(wm8350_gpio_init);
|
||||
|
||||
static void __exit wm8350_gpio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&wm8350_gpio_driver);
|
||||
}
|
||||
module_exit(wm8350_gpio_exit);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_DESCRIPTION("GPIO interface for WM8350 PMICs");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:wm8350-gpio");
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* wm8994-gpio.c -- gpiolib support for Wolfson WM8994
|
||||
*
|
||||
* 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/mfd/wm8994/core.h>
|
||||
#include <linux/mfd/wm8994/pdata.h>
|
||||
#include <linux/mfd/wm8994/gpio.h>
|
||||
#include <linux/mfd/wm8994/registers.h>
|
||||
|
||||
struct wm8994_gpio {
|
||||
struct wm8994 *wm8994;
|
||||
struct gpio_chip gpio_chip;
|
||||
};
|
||||
|
||||
static inline struct wm8994_gpio *to_wm8994_gpio(struct gpio_chip *chip)
|
||||
{
|
||||
return container_of(chip, struct wm8994_gpio, gpio_chip);
|
||||
}
|
||||
|
||||
static int wm8994_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
|
||||
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
|
||||
|
||||
return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
|
||||
WM8994_GPN_DIR, WM8994_GPN_DIR);
|
||||
}
|
||||
|
||||
static int wm8994_gpio_get(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
|
||||
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
|
||||
int ret;
|
||||
|
||||
ret = wm8994_reg_read(wm8994, WM8994_GPIO_1 + offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret & WM8994_GPN_LVL)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8994_gpio_direction_out(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
|
||||
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
|
||||
|
||||
return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
|
||||
WM8994_GPN_DIR, 0);
|
||||
}
|
||||
|
||||
static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
|
||||
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
|
||||
|
||||
if (value)
|
||||
value = WM8994_GPN_LVL;
|
||||
|
||||
wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip);
|
||||
struct wm8994 *wm8994 = wm8994_gpio->wm8994;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < chip->ngpio; i++) {
|
||||
int gpio = i + chip->base;
|
||||
int reg;
|
||||
const char *label;
|
||||
|
||||
/* We report the GPIO even if it's not requested since
|
||||
* we're also reporting things like alternate
|
||||
* functions which apply even when the GPIO is not in
|
||||
* use as a GPIO.
|
||||
*/
|
||||
label = gpiochip_is_requested(chip, i);
|
||||
if (!label)
|
||||
label = "Unrequested";
|
||||
|
||||
seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
|
||||
|
||||
reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i);
|
||||
if (reg < 0) {
|
||||
dev_err(wm8994->dev,
|
||||
"GPIO control %d read failed: %d\n",
|
||||
gpio, reg);
|
||||
seq_printf(s, "\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* No decode yet; note that GPIO2 is special */
|
||||
seq_printf(s, "(%x)\n", reg);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define wm8994_gpio_dbg_show NULL
|
||||
#endif
|
||||
|
||||
static struct gpio_chip template_chip = {
|
||||
.label = "wm8994",
|
||||
.owner = THIS_MODULE,
|
||||
.direction_input = wm8994_gpio_direction_in,
|
||||
.get = wm8994_gpio_get,
|
||||
.direction_output = wm8994_gpio_direction_out,
|
||||
.set = wm8994_gpio_set,
|
||||
.dbg_show = wm8994_gpio_dbg_show,
|
||||
.can_sleep = 1,
|
||||
};
|
||||
|
||||
static int __devinit wm8994_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
|
||||
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
|
||||
struct wm8994_gpio *wm8994_gpio;
|
||||
int ret;
|
||||
|
||||
wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
|
||||
if (wm8994_gpio == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
wm8994_gpio->wm8994 = wm8994;
|
||||
wm8994_gpio->gpio_chip = template_chip;
|
||||
wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
|
||||
wm8994_gpio->gpio_chip.dev = &pdev->dev;
|
||||
if (pdata && pdata->gpio_base)
|
||||
wm8994_gpio->gpio_chip.base = pdata->gpio_base;
|
||||
else
|
||||
wm8994_gpio->gpio_chip.base = -1;
|
||||
|
||||
ret = gpiochip_add(&wm8994_gpio->gpio_chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, wm8994_gpio);
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
kfree(wm8994_gpio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit wm8994_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8994_gpio *wm8994_gpio = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = gpiochip_remove(&wm8994_gpio->gpio_chip);
|
||||
if (ret == 0)
|
||||
kfree(wm8994_gpio);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver wm8994_gpio_driver = {
|
||||
.driver.name = "wm8994-gpio",
|
||||
.driver.owner = THIS_MODULE,
|
||||
.probe = wm8994_gpio_probe,
|
||||
.remove = __devexit_p(wm8994_gpio_remove),
|
||||
};
|
||||
|
||||
static int __init wm8994_gpio_init(void)
|
||||
{
|
||||
return platform_driver_register(&wm8994_gpio_driver);
|
||||
}
|
||||
subsys_initcall(wm8994_gpio_init);
|
||||
|
||||
static void __exit wm8994_gpio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&wm8994_gpio_driver);
|
||||
}
|
||||
module_exit(wm8994_gpio_exit);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_DESCRIPTION("GPIO interface for WM8994");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:wm8994-gpio");
|
|
@ -106,6 +106,8 @@ config I2C_I801
|
|||
config I2C_ISCH
|
||||
tristate "Intel SCH SMBus 1.0"
|
||||
depends on PCI
|
||||
select MFD_CORE
|
||||
select LPC_SCH
|
||||
help
|
||||
Say Y here if you want to use SMBus controller on the Intel SCH
|
||||
based systems.
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/stddef.h>
|
||||
|
@ -46,12 +46,6 @@
|
|||
#define SMBHSTDAT1 (7 + sch_smba)
|
||||
#define SMBBLKDAT (0x20 + sch_smba)
|
||||
|
||||
/* count for request_region */
|
||||
#define SMBIOSIZE 64
|
||||
|
||||
/* PCI Address Constants */
|
||||
#define SMBBA_SCH 0x40
|
||||
|
||||
/* Other settings */
|
||||
#define MAX_TIMEOUT 500
|
||||
|
||||
|
@ -63,7 +57,6 @@
|
|||
#define SCH_BLOCK_DATA 0x05
|
||||
|
||||
static unsigned short sch_smba;
|
||||
static struct pci_driver sch_driver;
|
||||
static struct i2c_adapter sch_adapter;
|
||||
|
||||
/*
|
||||
|
@ -256,37 +249,23 @@ static struct i2c_adapter sch_adapter = {
|
|||
.algo = &smbus_algorithm,
|
||||
};
|
||||
|
||||
static const struct pci_device_id sch_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, sch_ids);
|
||||
|
||||
static int __devinit sch_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *id)
|
||||
static int __devinit smbus_sch_probe(struct platform_device *dev)
|
||||
{
|
||||
struct resource *res;
|
||||
int retval;
|
||||
unsigned int smba;
|
||||
|
||||
pci_read_config_dword(dev, SMBBA_SCH, &smba);
|
||||
if (!(smba & (1 << 31))) {
|
||||
dev_err(&dev->dev, "SMBus I/O space disabled!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
res = platform_get_resource(dev, IORESOURCE_IO, 0);
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
|
||||
sch_smba = (unsigned short)smba;
|
||||
if (sch_smba == 0) {
|
||||
dev_err(&dev->dev, "SMBus base address uninitialized!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (acpi_check_region(sch_smba, SMBIOSIZE, sch_driver.name))
|
||||
return -ENODEV;
|
||||
if (!request_region(sch_smba, SMBIOSIZE, sch_driver.name)) {
|
||||
if (!request_region(res->start, resource_size(res), dev->name)) {
|
||||
dev_err(&dev->dev, "SMBus region 0x%x already in use!\n",
|
||||
sch_smba);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
sch_smba = res->start;
|
||||
|
||||
dev_dbg(&dev->dev, "SMBA = 0x%X\n", sch_smba);
|
||||
|
||||
/* set up the sysfs linkage to our parent device */
|
||||
|
@ -298,37 +277,43 @@ static int __devinit sch_probe(struct pci_dev *dev,
|
|||
retval = i2c_add_adapter(&sch_adapter);
|
||||
if (retval) {
|
||||
dev_err(&dev->dev, "Couldn't register adapter!\n");
|
||||
release_region(sch_smba, SMBIOSIZE);
|
||||
release_region(res->start, resource_size(res));
|
||||
sch_smba = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __devexit sch_remove(struct pci_dev *dev)
|
||||
static int __devexit smbus_sch_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
if (sch_smba) {
|
||||
i2c_del_adapter(&sch_adapter);
|
||||
release_region(sch_smba, SMBIOSIZE);
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
release_region(res->start, resource_size(res));
|
||||
sch_smba = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_driver sch_driver = {
|
||||
.name = "isch_smbus",
|
||||
.id_table = sch_ids,
|
||||
.probe = sch_probe,
|
||||
.remove = __devexit_p(sch_remove),
|
||||
static struct platform_driver smbus_sch_driver = {
|
||||
.driver = {
|
||||
.name = "isch_smbus",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = smbus_sch_probe,
|
||||
.remove = __devexit_p(smbus_sch_remove),
|
||||
};
|
||||
|
||||
static int __init i2c_sch_init(void)
|
||||
{
|
||||
return pci_register_driver(&sch_driver);
|
||||
return platform_driver_register(&smbus_sch_driver);
|
||||
}
|
||||
|
||||
static void __exit i2c_sch_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&sch_driver);
|
||||
platform_driver_unregister(&smbus_sch_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com>");
|
||||
|
@ -337,3 +322,4 @@ MODULE_LICENSE("GPL");
|
|||
|
||||
module_init(i2c_sch_init);
|
||||
module_exit(i2c_sch_exit);
|
||||
MODULE_ALIAS("platform:isch_smbus");
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 88pm860x_onkey.c - Marvell 88PM860x ONKEY driver
|
||||
*
|
||||
* Copyright (C) 2009-2010 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of this
|
||||
* archive for more details.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
#define PM8607_WAKEUP 0x0b
|
||||
|
||||
#define LONG_ONKEY_EN (1 << 1)
|
||||
#define ONKEY_STATUS (1 << 0)
|
||||
|
||||
struct pm860x_onkey_info {
|
||||
struct input_dev *idev;
|
||||
struct pm860x_chip *chip;
|
||||
struct i2c_client *i2c;
|
||||
struct device *dev;
|
||||
int irq;
|
||||
};
|
||||
|
||||
/* 88PM860x gives us an interrupt when ONKEY is held */
|
||||
static irqreturn_t pm860x_onkey_handler(int irq, void *data)
|
||||
{
|
||||
struct pm860x_onkey_info *info = data;
|
||||
int ret;
|
||||
|
||||
ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
|
||||
ret &= ONKEY_STATUS;
|
||||
input_report_key(info->idev, KEY_POWER, ret);
|
||||
input_sync(info->idev);
|
||||
|
||||
/* Enable 8-second long onkey detection */
|
||||
pm860x_set_bits(info->i2c, PM8607_WAKEUP, 3, LONG_ONKEY_EN);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int __devinit pm860x_onkey_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm860x_onkey_info *info;
|
||||
int irq, ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ resource!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
info = kzalloc(sizeof(struct pm860x_onkey_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
info->chip = chip;
|
||||
info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
|
||||
info->dev = &pdev->dev;
|
||||
info->irq = irq + chip->irq_base;
|
||||
|
||||
info->idev = input_allocate_device();
|
||||
if (!info->idev) {
|
||||
dev_err(chip->dev, "Failed to allocate input dev\n");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
info->idev->name = "88pm860x_on";
|
||||
info->idev->phys = "88pm860x_on/input0";
|
||||
info->idev->id.bustype = BUS_I2C;
|
||||
info->idev->dev.parent = &pdev->dev;
|
||||
info->irq = irq;
|
||||
info->idev->evbit[0] = BIT_MASK(EV_KEY);
|
||||
info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
|
||||
|
||||
ret = input_register_device(info->idev);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Can't register input device: %d\n", ret);
|
||||
goto out_reg;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(info->irq, NULL, pm860x_onkey_handler,
|
||||
IRQF_ONESHOT, "onkey", info);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
|
||||
info->irq, ret);
|
||||
goto out_irq;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
return 0;
|
||||
|
||||
out_irq:
|
||||
input_unregister_device(info->idev);
|
||||
kfree(info);
|
||||
return ret;
|
||||
|
||||
out_reg:
|
||||
input_free_device(info->idev);
|
||||
out:
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pm860x_onkey_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_onkey_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
free_irq(info->irq, info);
|
||||
input_unregister_device(info->idev);
|
||||
kfree(info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pm860x_onkey_driver = {
|
||||
.driver = {
|
||||
.name = "88pm860x-onkey",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_onkey_probe,
|
||||
.remove = __devexit_p(pm860x_onkey_remove),
|
||||
};
|
||||
|
||||
static int __init pm860x_onkey_init(void)
|
||||
{
|
||||
return platform_driver_register(&pm860x_onkey_driver);
|
||||
}
|
||||
module_init(pm860x_onkey_init);
|
||||
|
||||
static void __exit pm860x_onkey_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pm860x_onkey_driver);
|
||||
}
|
||||
module_exit(pm860x_onkey_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Marvell 88PM860x ONKEY driver");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -12,6 +12,16 @@ menuconfig INPUT_MISC
|
|||
|
||||
if INPUT_MISC
|
||||
|
||||
config INPUT_88PM860X_ONKEY
|
||||
tristate "88PM860x ONKEY support"
|
||||
depends on MFD_88PM860X
|
||||
help
|
||||
Support the ONKEY of Marvell 88PM860x PMICs as an input device
|
||||
reporting power button status.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called 88pm860x_onkey.
|
||||
|
||||
config INPUT_PCSPKR
|
||||
tristate "PC Speaker support"
|
||||
depends on PCSPKR_PLATFORM
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
# Each configuration option enables a list of files.
|
||||
|
||||
obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o
|
||||
obj-$(CONFIG_INPUT_APANEL) += apanel.o
|
||||
obj-$(CONFIG_INPUT_ATI_REMOTE) += ati_remote.o
|
||||
obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Touchscreen driver for Marvell 88PM860x
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
#define MEAS_LEN (8)
|
||||
#define ACCURATE_BIT (12)
|
||||
|
||||
/* touch register */
|
||||
#define MEAS_EN3 (0x52)
|
||||
|
||||
#define MEAS_TSIX_1 (0x8D)
|
||||
#define MEAS_TSIX_2 (0x8E)
|
||||
#define MEAS_TSIY_1 (0x8F)
|
||||
#define MEAS_TSIY_2 (0x90)
|
||||
#define MEAS_TSIZ1_1 (0x91)
|
||||
#define MEAS_TSIZ1_2 (0x92)
|
||||
#define MEAS_TSIZ2_1 (0x93)
|
||||
#define MEAS_TSIZ2_2 (0x94)
|
||||
|
||||
/* bit definitions of touch */
|
||||
#define MEAS_PD_EN (1 << 3)
|
||||
#define MEAS_TSIX_EN (1 << 4)
|
||||
#define MEAS_TSIY_EN (1 << 5)
|
||||
#define MEAS_TSIZ1_EN (1 << 6)
|
||||
#define MEAS_TSIZ2_EN (1 << 7)
|
||||
|
||||
struct pm860x_touch {
|
||||
struct input_dev *idev;
|
||||
struct i2c_client *i2c;
|
||||
struct pm860x_chip *chip;
|
||||
int irq;
|
||||
int res_x; /* resistor of Xplate */
|
||||
};
|
||||
|
||||
static irqreturn_t pm860x_touch_handler(int irq, void *data)
|
||||
{
|
||||
struct pm860x_touch *touch = data;
|
||||
struct pm860x_chip *chip = touch->chip;
|
||||
unsigned char buf[MEAS_LEN];
|
||||
int x, y, pen_down;
|
||||
int z1, z2, rt = 0;
|
||||
int ret;
|
||||
|
||||
ret = pm860x_bulk_read(touch->i2c, MEAS_TSIX_1, MEAS_LEN, buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pen_down = buf[1] & (1 << 6);
|
||||
x = ((buf[0] & 0xFF) << 4) | (buf[1] & 0x0F);
|
||||
y = ((buf[2] & 0xFF) << 4) | (buf[3] & 0x0F);
|
||||
z1 = ((buf[4] & 0xFF) << 4) | (buf[5] & 0x0F);
|
||||
z2 = ((buf[6] & 0xFF) << 4) | (buf[7] & 0x0F);
|
||||
|
||||
if (pen_down) {
|
||||
if ((x != 0) && (z1 != 0) && (touch->res_x != 0)) {
|
||||
rt = z2 / z1 - 1;
|
||||
rt = (rt * touch->res_x * x) >> ACCURATE_BIT;
|
||||
dev_dbg(chip->dev, "z1:%d, z2:%d, rt:%d\n",
|
||||
z1, z2, rt);
|
||||
}
|
||||
input_report_abs(touch->idev, ABS_X, x);
|
||||
input_report_abs(touch->idev, ABS_Y, y);
|
||||
input_report_abs(touch->idev, ABS_PRESSURE, rt);
|
||||
input_report_key(touch->idev, BTN_TOUCH, 1);
|
||||
dev_dbg(chip->dev, "pen down at [%d, %d].\n", x, y);
|
||||
} else {
|
||||
input_report_abs(touch->idev, ABS_PRESSURE, 0);
|
||||
input_report_key(touch->idev, BTN_TOUCH, 0);
|
||||
dev_dbg(chip->dev, "pen release\n");
|
||||
}
|
||||
input_sync(touch->idev);
|
||||
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int pm860x_touch_open(struct input_dev *dev)
|
||||
{
|
||||
struct pm860x_touch *touch = input_get_drvdata(dev);
|
||||
int data, ret;
|
||||
|
||||
data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN
|
||||
| MEAS_TSIZ1_EN | MEAS_TSIZ2_EN;
|
||||
ret = pm860x_set_bits(touch->i2c, MEAS_EN3, data, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pm860x_touch_close(struct input_dev *dev)
|
||||
{
|
||||
struct pm860x_touch *touch = input_get_drvdata(dev);
|
||||
int data;
|
||||
|
||||
data = MEAS_PD_EN | MEAS_TSIX_EN | MEAS_TSIY_EN
|
||||
| MEAS_TSIZ1_EN | MEAS_TSIZ2_EN;
|
||||
pm860x_set_bits(touch->i2c, MEAS_EN3, data, 0);
|
||||
}
|
||||
|
||||
static int __devinit pm860x_touch_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm860x_platform_data *pm860x_pdata = \
|
||||
pdev->dev.parent->platform_data;
|
||||
struct pm860x_touch_pdata *pdata = NULL;
|
||||
struct pm860x_touch *touch;
|
||||
int irq, ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ resource!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pm860x_pdata) {
|
||||
dev_err(&pdev->dev, "platform data is missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pdata = pm860x_pdata->touch;
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "touchscreen data is missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
touch = kzalloc(sizeof(struct pm860x_touch), GFP_KERNEL);
|
||||
if (touch == NULL)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(&pdev->dev, touch);
|
||||
|
||||
touch->idev = input_allocate_device();
|
||||
if (touch->idev == NULL) {
|
||||
dev_err(&pdev->dev, "Failed to allocate input device!\n");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
touch->idev->name = "88pm860x-touch";
|
||||
touch->idev->phys = "88pm860x/input0";
|
||||
touch->idev->id.bustype = BUS_I2C;
|
||||
touch->idev->dev.parent = &pdev->dev;
|
||||
touch->idev->open = pm860x_touch_open;
|
||||
touch->idev->close = pm860x_touch_close;
|
||||
touch->chip = chip;
|
||||
touch->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
|
||||
touch->irq = irq + chip->irq_base;
|
||||
touch->res_x = pdata->res_x;
|
||||
input_set_drvdata(touch->idev, touch);
|
||||
|
||||
ret = request_threaded_irq(touch->irq, NULL, pm860x_touch_handler,
|
||||
IRQF_ONESHOT, "touch", touch);
|
||||
if (ret < 0)
|
||||
goto out_irq;
|
||||
|
||||
__set_bit(EV_ABS, touch->idev->evbit);
|
||||
__set_bit(ABS_X, touch->idev->absbit);
|
||||
__set_bit(ABS_Y, touch->idev->absbit);
|
||||
__set_bit(ABS_PRESSURE, touch->idev->absbit);
|
||||
__set_bit(EV_SYN, touch->idev->evbit);
|
||||
__set_bit(EV_KEY, touch->idev->evbit);
|
||||
__set_bit(BTN_TOUCH, touch->idev->keybit);
|
||||
|
||||
input_set_abs_params(touch->idev, ABS_X, 0, 1 << ACCURATE_BIT, 0, 0);
|
||||
input_set_abs_params(touch->idev, ABS_Y, 0, 1 << ACCURATE_BIT, 0, 0);
|
||||
input_set_abs_params(touch->idev, ABS_PRESSURE, 0, 1 << ACCURATE_BIT,
|
||||
0, 0);
|
||||
|
||||
ret = input_register_device(touch->idev);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to register touch!\n");
|
||||
goto out_rg;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, touch);
|
||||
return 0;
|
||||
out_rg:
|
||||
free_irq(touch->irq, touch);
|
||||
out_irq:
|
||||
input_free_device(touch->idev);
|
||||
out:
|
||||
kfree(touch);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pm860x_touch_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_touch *touch = platform_get_drvdata(pdev);
|
||||
|
||||
input_unregister_device(touch->idev);
|
||||
free_irq(touch->irq, touch);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(touch);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pm860x_touch_driver = {
|
||||
.driver = {
|
||||
.name = "88pm860x-touch",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_touch_probe,
|
||||
.remove = __devexit_p(pm860x_touch_remove),
|
||||
};
|
||||
|
||||
static int __init pm860x_touch_init(void)
|
||||
{
|
||||
return platform_driver_register(&pm860x_touch_driver);
|
||||
}
|
||||
module_init(pm860x_touch_init);
|
||||
|
||||
static void __exit pm860x_touch_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pm860x_touch_driver);
|
||||
}
|
||||
module_exit(pm860x_touch_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Touchscreen driver for Marvell Semiconductor 88PM860x");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:88pm860x-touch");
|
||||
|
|
@ -11,6 +11,18 @@ menuconfig INPUT_TOUCHSCREEN
|
|||
|
||||
if INPUT_TOUCHSCREEN
|
||||
|
||||
config TOUCHSCREEN_88PM860X
|
||||
tristate "Marvell 88PM860x touchscreen"
|
||||
depends on MFD_88PM860X
|
||||
help
|
||||
Say Y here if you have a 88PM860x PMIC and want to enable
|
||||
support for the built-in touchscreen.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called 88pm860x-ts.
|
||||
|
||||
config TOUCHSCREEN_ADS7846
|
||||
tristate "ADS7846/TSC2046 and ADS7843 based touchscreens"
|
||||
depends on SPI_MASTER
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
wm97xx-ts-y := wm97xx-core.o
|
||||
|
||||
obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o
|
||||
|
|
|
@ -17,6 +17,13 @@ config LEDS_CLASS
|
|||
|
||||
comment "LED drivers"
|
||||
|
||||
config LEDS_88PM860X
|
||||
tristate "LED Support for Marvell 88PM860x PMIC"
|
||||
depends on LEDS_CLASS && MFD_88PM860X
|
||||
help
|
||||
This option enables support for on-chip LED drivers found on Marvell
|
||||
Semiconductor 88PM8606 PMIC.
|
||||
|
||||
config LEDS_ATMEL_PWM
|
||||
tristate "LED Support using Atmel PWM outputs"
|
||||
depends on LEDS_CLASS && ATMEL_PWM
|
||||
|
|
|
@ -5,6 +5,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o
|
|||
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
|
||||
|
||||
# LED Platform Drivers
|
||||
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
|
||||
obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o
|
||||
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
|
||||
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* LED driver for Marvell 88PM860x
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
#define LED_PWM_SHIFT (3)
|
||||
#define LED_PWM_MASK (0x1F)
|
||||
#define LED_CURRENT_MASK (0x07 << 5)
|
||||
|
||||
#define LED_BLINK_ON_MASK (0x07)
|
||||
#define LED_BLINK_PERIOD_MASK (0x0F << 3)
|
||||
#define LED_BLINK_MASK (0x7F)
|
||||
|
||||
#define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
|
||||
#define LED_BLINK_PERIOD(x) ((x & 0xF) * 530 + 930)
|
||||
#define LED_BLINK_ON_MIN LED_BLINK_ON(0)
|
||||
#define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
|
||||
#define LED_BLINK_PERIOD_MIN LED_BLINK_PERIOD(0)
|
||||
#define LED_BLINK_PERIOD_MAX LED_BLINK_PERIOD(0xE)
|
||||
#define LED_TO_ON(x) ((x - 66) / 66)
|
||||
#define LED_TO_PERIOD(x) ((x - 930) / 530)
|
||||
|
||||
#define LED1_BLINK_EN (1 << 1)
|
||||
#define LED2_BLINK_EN (1 << 2)
|
||||
|
||||
enum {
|
||||
SET_BRIGHTNESS,
|
||||
SET_BLINK,
|
||||
};
|
||||
|
||||
struct pm860x_led {
|
||||
struct led_classdev cdev;
|
||||
struct i2c_client *i2c;
|
||||
struct work_struct work;
|
||||
struct pm860x_chip *chip;
|
||||
struct mutex lock;
|
||||
char name[MFD_NAME_SIZE];
|
||||
|
||||
int port;
|
||||
int iset;
|
||||
int command;
|
||||
int offset;
|
||||
unsigned char brightness;
|
||||
unsigned char current_brightness;
|
||||
|
||||
int blink_data;
|
||||
int blink_time;
|
||||
int blink_on;
|
||||
int blink_off;
|
||||
};
|
||||
|
||||
/* return offset of color register */
|
||||
static inline int __led_off(int port)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (port) {
|
||||
case PM8606_LED1_RED:
|
||||
case PM8606_LED1_GREEN:
|
||||
case PM8606_LED1_BLUE:
|
||||
ret = port - PM8606_LED1_RED + PM8606_RGB1B;
|
||||
break;
|
||||
case PM8606_LED2_RED:
|
||||
case PM8606_LED2_GREEN:
|
||||
case PM8606_LED2_BLUE:
|
||||
ret = port - PM8606_LED2_RED + PM8606_RGB2B;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* return offset of blink register */
|
||||
static inline int __blink_off(int port)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (port) {
|
||||
case PM8606_LED1_RED:
|
||||
case PM8606_LED1_GREEN:
|
||||
case PM8606_LED1_BLUE:
|
||||
ret = PM8606_RGB1A;
|
||||
case PM8606_LED2_RED:
|
||||
case PM8606_LED2_GREEN:
|
||||
case PM8606_LED2_BLUE:
|
||||
ret = PM8606_RGB2A;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int __blink_ctl_mask(int port)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (port) {
|
||||
case PM8606_LED1_RED:
|
||||
case PM8606_LED1_GREEN:
|
||||
case PM8606_LED1_BLUE:
|
||||
ret = LED1_BLINK_EN;
|
||||
break;
|
||||
case PM8606_LED2_RED:
|
||||
case PM8606_LED2_GREEN:
|
||||
case PM8606_LED2_BLUE:
|
||||
ret = LED2_BLINK_EN;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __led_set(struct pm860x_led *led, int command)
|
||||
{
|
||||
struct pm860x_chip *chip = led->chip;
|
||||
int mask, ret;
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
switch (command) {
|
||||
case SET_BRIGHTNESS:
|
||||
if ((led->current_brightness == 0) && led->brightness) {
|
||||
if (led->iset) {
|
||||
ret = pm860x_set_bits(led->i2c, led->offset,
|
||||
LED_CURRENT_MASK, led->iset);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
} else if (led->brightness == 0) {
|
||||
ret = pm860x_set_bits(led->i2c, led->offset,
|
||||
LED_CURRENT_MASK, 0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
ret = pm860x_set_bits(led->i2c, led->offset, LED_PWM_MASK,
|
||||
led->brightness);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
led->current_brightness = led->brightness;
|
||||
dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
|
||||
led->offset, led->brightness);
|
||||
break;
|
||||
case SET_BLINK:
|
||||
ret = pm860x_set_bits(led->i2c, led->offset,
|
||||
LED_BLINK_MASK, led->blink_data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
mask = __blink_ctl_mask(led->port);
|
||||
ret = pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
dev_dbg(chip->dev, "LED blink delay on:%dms, delay off:%dms\n",
|
||||
led->blink_on, led->blink_off);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&led->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pm860x_led_work(struct work_struct *work)
|
||||
{
|
||||
struct pm860x_led *led;
|
||||
|
||||
led = container_of(work, struct pm860x_led, work);
|
||||
__led_set(led, led->command);
|
||||
}
|
||||
|
||||
static void pm860x_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
|
||||
|
||||
data->offset = __led_off(data->port);
|
||||
data->brightness = value >> 3;
|
||||
data->command = SET_BRIGHTNESS;
|
||||
schedule_work(&data->work);
|
||||
}
|
||||
|
||||
static int pm860x_led_blink(struct led_classdev *cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
|
||||
int period, on;
|
||||
|
||||
on = *delay_on;
|
||||
if ((on < LED_BLINK_ON_MIN) || (on > LED_BLINK_ON_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
on = LED_TO_ON(on);
|
||||
on = LED_BLINK_ON(on);
|
||||
|
||||
period = on + *delay_off;
|
||||
if ((period < LED_BLINK_PERIOD_MIN) || (period > LED_BLINK_PERIOD_MAX))
|
||||
return -EINVAL;
|
||||
period = LED_TO_PERIOD(period);
|
||||
period = LED_BLINK_PERIOD(period);
|
||||
|
||||
data->offset = __blink_off(data->port);
|
||||
data->blink_on = on;
|
||||
data->blink_off = period - data->blink_on;
|
||||
data->blink_data = (period << 3) | data->blink_on;
|
||||
data->command = SET_BLINK;
|
||||
schedule_work(&data->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __check_device(struct pm860x_led_pdata *pdata, char *name)
|
||||
{
|
||||
struct pm860x_led_pdata *p = pdata;
|
||||
int ret = -EINVAL;
|
||||
|
||||
while (p && p->id) {
|
||||
if ((p->id != PM8606_ID_LED) || (p->flags < 0))
|
||||
break;
|
||||
|
||||
if (!strncmp(name, pm860x_led_name[p->flags],
|
||||
MFD_NAME_SIZE)) {
|
||||
ret = (int)p->flags;
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm860x_platform_data *pm860x_pdata;
|
||||
struct pm860x_led_pdata *pdata;
|
||||
struct pm860x_led *data;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "No I/O resource!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pdev->dev.parent->platform_data) {
|
||||
pm860x_pdata = pdev->dev.parent->platform_data;
|
||||
pdata = pm860x_pdata->led;
|
||||
} else
|
||||
pdata = NULL;
|
||||
|
||||
data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
strncpy(data->name, res->name, MFD_NAME_SIZE);
|
||||
dev_set_drvdata(&pdev->dev, data);
|
||||
data->chip = chip;
|
||||
data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
|
||||
data->iset = pdata->iset;
|
||||
data->port = __check_device(pdata, data->name);
|
||||
if (data->port < 0)
|
||||
return -EINVAL;
|
||||
|
||||
data->current_brightness = 0;
|
||||
data->cdev.name = data->name;
|
||||
data->cdev.brightness_set = pm860x_led_set;
|
||||
data->cdev.blink_set = pm860x_led_blink;
|
||||
mutex_init(&data->lock);
|
||||
INIT_WORK(&data->work, pm860x_led_work);
|
||||
|
||||
ret = led_classdev_register(chip->dev, &data->cdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_led *data = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&data->cdev);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pm860x_led_driver = {
|
||||
.driver = {
|
||||
.name = "88pm860x-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_led_probe,
|
||||
.remove = pm860x_led_remove,
|
||||
};
|
||||
|
||||
static int __devinit pm860x_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&pm860x_led_driver);
|
||||
}
|
||||
module_init(pm860x_led_init);
|
||||
|
||||
static void __devexit pm860x_led_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pm860x_led_driver);
|
||||
}
|
||||
module_exit(pm860x_led_exit);
|
||||
|
||||
MODULE_DESCRIPTION("LED driver for Marvell PM860x");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:88pm860x-led");
|
|
@ -1,302 +0,0 @@
|
|||
/*
|
||||
* Base driver for Marvell 88PM8607
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/88pm8607.h>
|
||||
|
||||
|
||||
#define PM8607_REG_RESOURCE(_start, _end) \
|
||||
{ \
|
||||
.start = PM8607_##_start, \
|
||||
.end = PM8607_##_end, \
|
||||
.flags = IORESOURCE_IO, \
|
||||
}
|
||||
|
||||
static struct resource pm8607_regulator_resources[] = {
|
||||
PM8607_REG_RESOURCE(BUCK1, BUCK1),
|
||||
PM8607_REG_RESOURCE(BUCK2, BUCK2),
|
||||
PM8607_REG_RESOURCE(BUCK3, BUCK3),
|
||||
PM8607_REG_RESOURCE(LDO1, LDO1),
|
||||
PM8607_REG_RESOURCE(LDO2, LDO2),
|
||||
PM8607_REG_RESOURCE(LDO3, LDO3),
|
||||
PM8607_REG_RESOURCE(LDO4, LDO4),
|
||||
PM8607_REG_RESOURCE(LDO5, LDO5),
|
||||
PM8607_REG_RESOURCE(LDO6, LDO6),
|
||||
PM8607_REG_RESOURCE(LDO7, LDO7),
|
||||
PM8607_REG_RESOURCE(LDO8, LDO8),
|
||||
PM8607_REG_RESOURCE(LDO9, LDO9),
|
||||
PM8607_REG_RESOURCE(LDO10, LDO10),
|
||||
PM8607_REG_RESOURCE(LDO12, LDO12),
|
||||
PM8607_REG_RESOURCE(LDO14, LDO14),
|
||||
};
|
||||
|
||||
#define PM8607_REG_DEVS(_name, _id) \
|
||||
{ \
|
||||
.name = "88pm8607-" #_name, \
|
||||
.num_resources = 1, \
|
||||
.resources = &pm8607_regulator_resources[PM8607_ID_##_id], \
|
||||
}
|
||||
|
||||
static struct mfd_cell pm8607_devs[] = {
|
||||
PM8607_REG_DEVS(buck1, BUCK1),
|
||||
PM8607_REG_DEVS(buck2, BUCK2),
|
||||
PM8607_REG_DEVS(buck3, BUCK3),
|
||||
PM8607_REG_DEVS(ldo1, LDO1),
|
||||
PM8607_REG_DEVS(ldo2, LDO2),
|
||||
PM8607_REG_DEVS(ldo3, LDO3),
|
||||
PM8607_REG_DEVS(ldo4, LDO4),
|
||||
PM8607_REG_DEVS(ldo5, LDO5),
|
||||
PM8607_REG_DEVS(ldo6, LDO6),
|
||||
PM8607_REG_DEVS(ldo7, LDO7),
|
||||
PM8607_REG_DEVS(ldo8, LDO8),
|
||||
PM8607_REG_DEVS(ldo9, LDO9),
|
||||
PM8607_REG_DEVS(ldo10, LDO10),
|
||||
PM8607_REG_DEVS(ldo12, LDO12),
|
||||
PM8607_REG_DEVS(ldo14, LDO14),
|
||||
};
|
||||
|
||||
static inline int pm8607_read_device(struct pm8607_chip *chip,
|
||||
int reg, int bytes, void *dest)
|
||||
{
|
||||
struct i2c_client *i2c = chip->client;
|
||||
unsigned char data;
|
||||
int ret;
|
||||
|
||||
data = (unsigned char)reg;
|
||||
ret = i2c_master_send(i2c, &data, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_master_recv(i2c, dest, bytes);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pm8607_write_device(struct pm8607_chip *chip,
|
||||
int reg, int bytes, void *src)
|
||||
{
|
||||
struct i2c_client *i2c = chip->client;
|
||||
unsigned char buf[bytes + 1];
|
||||
int ret;
|
||||
|
||||
buf[0] = (unsigned char)reg;
|
||||
memcpy(&buf[1], src, bytes);
|
||||
|
||||
ret = i2c_master_send(i2c, buf, bytes + 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pm8607_reg_read(struct pm8607_chip *chip, int reg)
|
||||
{
|
||||
unsigned char data;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = chip->read(chip, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return (int)data;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8607_reg_read);
|
||||
|
||||
int pm8607_reg_write(struct pm8607_chip *chip, int reg,
|
||||
unsigned char data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = chip->write(chip, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8607_reg_write);
|
||||
|
||||
int pm8607_bulk_read(struct pm8607_chip *chip, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = chip->read(chip, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8607_bulk_read);
|
||||
|
||||
int pm8607_bulk_write(struct pm8607_chip *chip, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = chip->write(chip, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8607_bulk_write);
|
||||
|
||||
int pm8607_set_bits(struct pm8607_chip *chip, int reg,
|
||||
unsigned char mask, unsigned char data)
|
||||
{
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = chip->read(chip, reg, 1, &value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
value &= ~mask;
|
||||
value |= data;
|
||||
ret = chip->write(chip, reg, 1, &value);
|
||||
out:
|
||||
mutex_unlock(&chip->io_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8607_set_bits);
|
||||
|
||||
|
||||
static const struct i2c_device_id pm8607_id_table[] = {
|
||||
{ "88PM8607", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pm8607_id_table);
|
||||
|
||||
|
||||
static int __devinit pm8607_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct pm8607_platform_data *pdata = client->dev.platform_data;
|
||||
struct pm8607_chip *chip;
|
||||
int i, count;
|
||||
int ret;
|
||||
|
||||
chip = kzalloc(sizeof(struct pm8607_chip), GFP_KERNEL);
|
||||
if (chip == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->client = client;
|
||||
chip->dev = &client->dev;
|
||||
chip->read = pm8607_read_device;
|
||||
chip->write = pm8607_write_device;
|
||||
i2c_set_clientdata(client, chip);
|
||||
|
||||
mutex_init(&chip->io_lock);
|
||||
dev_set_drvdata(chip->dev, chip);
|
||||
|
||||
ret = pm8607_reg_read(chip, PM8607_CHIP_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if ((ret & CHIP_ID_MASK) == CHIP_ID)
|
||||
dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n",
|
||||
ret);
|
||||
else {
|
||||
dev_err(chip->dev, "Failed to detect Marvell 88PM8607. "
|
||||
"Chip ID: %02x\n", ret);
|
||||
goto out;
|
||||
}
|
||||
chip->chip_id = ret;
|
||||
|
||||
ret = pm8607_reg_read(chip, PM8607_BUCK3);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if (ret & PM8607_BUCK3_DOUBLE)
|
||||
chip->buck3_double = 1;
|
||||
|
||||
ret = pm8607_reg_read(chip, PM8607_MISC1);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if (pdata->i2c_port == PI2C_PORT)
|
||||
ret |= PM8607_MISC1_PI2C;
|
||||
else
|
||||
ret &= ~PM8607_MISC1_PI2C;
|
||||
ret = pm8607_reg_write(chip, PM8607_MISC1, ret);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to write MISC1 register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
count = ARRAY_SIZE(pm8607_devs);
|
||||
for (i = 0; i < count; i++) {
|
||||
ret = mfd_add_devices(chip->dev, i, &pm8607_devs[i],
|
||||
1, NULL, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(chip->dev, "Failed to add subdevs\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
i2c_set_clientdata(client, NULL);
|
||||
kfree(chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pm8607_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pm8607_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
mfd_remove_devices(chip->dev);
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pm8607_driver = {
|
||||
.driver = {
|
||||
.name = "88PM8607",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm8607_probe,
|
||||
.remove = __devexit_p(pm8607_remove),
|
||||
.id_table = pm8607_id_table,
|
||||
};
|
||||
|
||||
static int __init pm8607_init(void)
|
||||
{
|
||||
int ret;
|
||||
ret = i2c_add_driver(&pm8607_driver);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to register 88PM8607 I2C driver: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
subsys_initcall(pm8607_init);
|
||||
|
||||
static void __exit pm8607_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pm8607_driver);
|
||||
}
|
||||
module_exit(pm8607_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM8607");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,740 @@
|
|||
/*
|
||||
* Base driver for Marvell 88PM8607
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
#define INT_STATUS_NUM 3
|
||||
|
||||
char pm860x_backlight_name[][MFD_NAME_SIZE] = {
|
||||
"backlight-0",
|
||||
"backlight-1",
|
||||
"backlight-2",
|
||||
};
|
||||
EXPORT_SYMBOL(pm860x_backlight_name);
|
||||
|
||||
char pm860x_led_name[][MFD_NAME_SIZE] = {
|
||||
"led0-red",
|
||||
"led0-green",
|
||||
"led0-blue",
|
||||
"led1-red",
|
||||
"led1-green",
|
||||
"led1-blue",
|
||||
};
|
||||
EXPORT_SYMBOL(pm860x_led_name);
|
||||
|
||||
#define PM8606_BACKLIGHT_RESOURCE(_i, _x) \
|
||||
{ \
|
||||
.name = pm860x_backlight_name[_i], \
|
||||
.start = PM8606_##_x, \
|
||||
.end = PM8606_##_x, \
|
||||
.flags = IORESOURCE_IO, \
|
||||
}
|
||||
|
||||
static struct resource backlight_resources[] = {
|
||||
PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT1, WLED1A),
|
||||
PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT2, WLED2A),
|
||||
PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT3, WLED3A),
|
||||
};
|
||||
|
||||
#define PM8606_BACKLIGHT_DEVS(_i) \
|
||||
{ \
|
||||
.name = "88pm860x-backlight", \
|
||||
.num_resources = 1, \
|
||||
.resources = &backlight_resources[_i], \
|
||||
.id = _i, \
|
||||
}
|
||||
|
||||
static struct mfd_cell backlight_devs[] = {
|
||||
PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT1),
|
||||
PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT2),
|
||||
PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT3),
|
||||
};
|
||||
|
||||
#define PM8606_LED_RESOURCE(_i, _x) \
|
||||
{ \
|
||||
.name = pm860x_led_name[_i], \
|
||||
.start = PM8606_##_x, \
|
||||
.end = PM8606_##_x, \
|
||||
.flags = IORESOURCE_IO, \
|
||||
}
|
||||
|
||||
static struct resource led_resources[] = {
|
||||
PM8606_LED_RESOURCE(PM8606_LED1_RED, RGB2B),
|
||||
PM8606_LED_RESOURCE(PM8606_LED1_GREEN, RGB2C),
|
||||
PM8606_LED_RESOURCE(PM8606_LED1_BLUE, RGB2D),
|
||||
PM8606_LED_RESOURCE(PM8606_LED2_RED, RGB1B),
|
||||
PM8606_LED_RESOURCE(PM8606_LED2_GREEN, RGB1C),
|
||||
PM8606_LED_RESOURCE(PM8606_LED2_BLUE, RGB1D),
|
||||
};
|
||||
|
||||
#define PM8606_LED_DEVS(_i) \
|
||||
{ \
|
||||
.name = "88pm860x-led", \
|
||||
.num_resources = 1, \
|
||||
.resources = &led_resources[_i], \
|
||||
.id = _i, \
|
||||
}
|
||||
|
||||
static struct mfd_cell led_devs[] = {
|
||||
PM8606_LED_DEVS(PM8606_LED1_RED),
|
||||
PM8606_LED_DEVS(PM8606_LED1_GREEN),
|
||||
PM8606_LED_DEVS(PM8606_LED1_BLUE),
|
||||
PM8606_LED_DEVS(PM8606_LED2_RED),
|
||||
PM8606_LED_DEVS(PM8606_LED2_GREEN),
|
||||
PM8606_LED_DEVS(PM8606_LED2_BLUE),
|
||||
};
|
||||
|
||||
static struct resource touch_resources[] = {
|
||||
{
|
||||
.start = PM8607_IRQ_PEN,
|
||||
.end = PM8607_IRQ_PEN,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell touch_devs[] = {
|
||||
{
|
||||
.name = "88pm860x-touch",
|
||||
.num_resources = 1,
|
||||
.resources = &touch_resources[0],
|
||||
},
|
||||
};
|
||||
|
||||
#define PM8607_REG_RESOURCE(_start, _end) \
|
||||
{ \
|
||||
.start = PM8607_##_start, \
|
||||
.end = PM8607_##_end, \
|
||||
.flags = IORESOURCE_IO, \
|
||||
}
|
||||
|
||||
static struct resource power_supply_resources[] = {
|
||||
{
|
||||
.name = "88pm860x-power",
|
||||
.start = PM8607_IRQ_CHG,
|
||||
.end = PM8607_IRQ_CHG,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell power_devs[] = {
|
||||
{
|
||||
.name = "88pm860x-power",
|
||||
.num_resources = 1,
|
||||
.resources = &power_supply_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource onkey_resources[] = {
|
||||
{
|
||||
.name = "88pm860x-onkey",
|
||||
.start = PM8607_IRQ_ONKEY,
|
||||
.end = PM8607_IRQ_ONKEY,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell onkey_devs[] = {
|
||||
{
|
||||
.name = "88pm860x-onkey",
|
||||
.num_resources = 1,
|
||||
.resources = &onkey_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource regulator_resources[] = {
|
||||
PM8607_REG_RESOURCE(BUCK1, BUCK1),
|
||||
PM8607_REG_RESOURCE(BUCK2, BUCK2),
|
||||
PM8607_REG_RESOURCE(BUCK3, BUCK3),
|
||||
PM8607_REG_RESOURCE(LDO1, LDO1),
|
||||
PM8607_REG_RESOURCE(LDO2, LDO2),
|
||||
PM8607_REG_RESOURCE(LDO3, LDO3),
|
||||
PM8607_REG_RESOURCE(LDO4, LDO4),
|
||||
PM8607_REG_RESOURCE(LDO5, LDO5),
|
||||
PM8607_REG_RESOURCE(LDO6, LDO6),
|
||||
PM8607_REG_RESOURCE(LDO7, LDO7),
|
||||
PM8607_REG_RESOURCE(LDO8, LDO8),
|
||||
PM8607_REG_RESOURCE(LDO9, LDO9),
|
||||
PM8607_REG_RESOURCE(LDO10, LDO10),
|
||||
PM8607_REG_RESOURCE(LDO12, LDO12),
|
||||
PM8607_REG_RESOURCE(LDO14, LDO14),
|
||||
};
|
||||
|
||||
#define PM8607_REG_DEVS(_name, _id) \
|
||||
{ \
|
||||
.name = "88pm8607-" #_name, \
|
||||
.num_resources = 1, \
|
||||
.resources = ®ulator_resources[PM8607_ID_##_id], \
|
||||
.id = PM8607_ID_##_id, \
|
||||
}
|
||||
|
||||
static struct mfd_cell regulator_devs[] = {
|
||||
PM8607_REG_DEVS(buck1, BUCK1),
|
||||
PM8607_REG_DEVS(buck2, BUCK2),
|
||||
PM8607_REG_DEVS(buck3, BUCK3),
|
||||
PM8607_REG_DEVS(ldo1, LDO1),
|
||||
PM8607_REG_DEVS(ldo2, LDO2),
|
||||
PM8607_REG_DEVS(ldo3, LDO3),
|
||||
PM8607_REG_DEVS(ldo4, LDO4),
|
||||
PM8607_REG_DEVS(ldo5, LDO5),
|
||||
PM8607_REG_DEVS(ldo6, LDO6),
|
||||
PM8607_REG_DEVS(ldo7, LDO7),
|
||||
PM8607_REG_DEVS(ldo8, LDO8),
|
||||
PM8607_REG_DEVS(ldo9, LDO9),
|
||||
PM8607_REG_DEVS(ldo10, LDO10),
|
||||
PM8607_REG_DEVS(ldo12, LDO12),
|
||||
PM8607_REG_DEVS(ldo14, LDO14),
|
||||
};
|
||||
|
||||
struct pm860x_irq_data {
|
||||
int reg;
|
||||
int mask_reg;
|
||||
int enable; /* enable or not */
|
||||
int offs; /* bit offset in mask register */
|
||||
};
|
||||
|
||||
static struct pm860x_irq_data pm860x_irqs[] = {
|
||||
[PM8607_IRQ_ONKEY] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[PM8607_IRQ_EXTON] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[PM8607_IRQ_CHG] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[PM8607_IRQ_BAT] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[PM8607_IRQ_RTC] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[PM8607_IRQ_CC] = {
|
||||
.reg = PM8607_INT_STATUS1,
|
||||
.mask_reg = PM8607_INT_MASK_1,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[PM8607_IRQ_VBAT] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[PM8607_IRQ_VCHG] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[PM8607_IRQ_VSYS] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[PM8607_IRQ_TINT] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[PM8607_IRQ_GPADC0] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[PM8607_IRQ_GPADC1] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[PM8607_IRQ_GPADC2] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 6,
|
||||
},
|
||||
[PM8607_IRQ_GPADC3] = {
|
||||
.reg = PM8607_INT_STATUS2,
|
||||
.mask_reg = PM8607_INT_MASK_2,
|
||||
.offs = 1 << 7,
|
||||
},
|
||||
[PM8607_IRQ_AUDIO_SHORT] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[PM8607_IRQ_PEN] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[PM8607_IRQ_HEADSET] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[PM8607_IRQ_HOOK] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[PM8607_IRQ_MICIN] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[PM8607_IRQ_CHG_FAIL] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[PM8607_IRQ_CHG_DONE] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 6,
|
||||
},
|
||||
[PM8607_IRQ_CHG_FAULT] = {
|
||||
.reg = PM8607_INT_STATUS3,
|
||||
.mask_reg = PM8607_INT_MASK_3,
|
||||
.offs = 1 << 7,
|
||||
},
|
||||
};
|
||||
|
||||
static inline struct pm860x_irq_data *irq_to_pm860x(struct pm860x_chip *chip,
|
||||
int irq)
|
||||
{
|
||||
return &pm860x_irqs[irq - chip->irq_base];
|
||||
}
|
||||
|
||||
static irqreturn_t pm860x_irq(int irq, void *data)
|
||||
{
|
||||
struct pm860x_chip *chip = data;
|
||||
struct pm860x_irq_data *irq_data;
|
||||
struct i2c_client *i2c;
|
||||
int read_reg = -1, value = 0;
|
||||
int i;
|
||||
|
||||
i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
|
||||
for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) {
|
||||
irq_data = &pm860x_irqs[i];
|
||||
if (read_reg != irq_data->reg) {
|
||||
read_reg = irq_data->reg;
|
||||
value = pm860x_reg_read(i2c, irq_data->reg);
|
||||
}
|
||||
if (value & irq_data->enable)
|
||||
handle_nested_irq(chip->irq_base + i);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void pm860x_irq_lock(unsigned int irq)
|
||||
{
|
||||
struct pm860x_chip *chip = get_irq_chip_data(irq);
|
||||
|
||||
mutex_lock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static void pm860x_irq_sync_unlock(unsigned int irq)
|
||||
{
|
||||
struct pm860x_chip *chip = get_irq_chip_data(irq);
|
||||
struct pm860x_irq_data *irq_data;
|
||||
struct i2c_client *i2c;
|
||||
static unsigned char cached[3] = {0x0, 0x0, 0x0};
|
||||
unsigned char mask[3];
|
||||
int i;
|
||||
|
||||
i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
|
||||
/* Load cached value. In initial, all IRQs are masked */
|
||||
for (i = 0; i < 3; i++)
|
||||
mask[i] = cached[i];
|
||||
for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) {
|
||||
irq_data = &pm860x_irqs[i];
|
||||
switch (irq_data->mask_reg) {
|
||||
case PM8607_INT_MASK_1:
|
||||
mask[0] &= ~irq_data->offs;
|
||||
mask[0] |= irq_data->enable;
|
||||
break;
|
||||
case PM8607_INT_MASK_2:
|
||||
mask[1] &= ~irq_data->offs;
|
||||
mask[1] |= irq_data->enable;
|
||||
break;
|
||||
case PM8607_INT_MASK_3:
|
||||
mask[2] &= ~irq_data->offs;
|
||||
mask[2] |= irq_data->enable;
|
||||
break;
|
||||
default:
|
||||
dev_err(chip->dev, "wrong IRQ\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* update mask into registers */
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (mask[i] != cached[i]) {
|
||||
cached[i] = mask[i];
|
||||
pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static void pm860x_irq_enable(unsigned int irq)
|
||||
{
|
||||
struct pm860x_chip *chip = get_irq_chip_data(irq);
|
||||
pm860x_irqs[irq - chip->irq_base].enable
|
||||
= pm860x_irqs[irq - chip->irq_base].offs;
|
||||
}
|
||||
|
||||
static void pm860x_irq_disable(unsigned int irq)
|
||||
{
|
||||
struct pm860x_chip *chip = get_irq_chip_data(irq);
|
||||
pm860x_irqs[irq - chip->irq_base].enable = 0;
|
||||
}
|
||||
|
||||
static struct irq_chip pm860x_irq_chip = {
|
||||
.name = "88pm860x",
|
||||
.bus_lock = pm860x_irq_lock,
|
||||
.bus_sync_unlock = pm860x_irq_sync_unlock,
|
||||
.enable = pm860x_irq_enable,
|
||||
.disable = pm860x_irq_disable,
|
||||
};
|
||||
|
||||
static int __devinit device_gpadc_init(struct pm860x_chip *chip,
|
||||
struct pm860x_platform_data *pdata)
|
||||
{
|
||||
struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
|
||||
: chip->companion;
|
||||
int use_gpadc = 0, data, ret;
|
||||
|
||||
/* initialize GPADC without activating it */
|
||||
|
||||
if (pdata && pdata->touch) {
|
||||
/* set GPADC MISC1 register */
|
||||
data = 0;
|
||||
data |= (pdata->touch->gpadc_prebias << 1)
|
||||
& PM8607_GPADC_PREBIAS_MASK;
|
||||
data |= (pdata->touch->slot_cycle << 3)
|
||||
& PM8607_GPADC_SLOT_CYCLE_MASK;
|
||||
data |= (pdata->touch->off_scale << 5)
|
||||
& PM8607_GPADC_OFF_SCALE_MASK;
|
||||
data |= (pdata->touch->sw_cal << 7)
|
||||
& PM8607_GPADC_SW_CAL_MASK;
|
||||
if (data) {
|
||||
ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
/* set tsi prebias time */
|
||||
if (pdata->touch->tsi_prebias) {
|
||||
data = pdata->touch->tsi_prebias;
|
||||
ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
/* set prebias & prechg time of pen detect */
|
||||
data = 0;
|
||||
data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK;
|
||||
data |= (pdata->touch->pen_prechg << 5)
|
||||
& PM8607_PD_PRECHG_MASK;
|
||||
if (data) {
|
||||
ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
use_gpadc = 1;
|
||||
}
|
||||
|
||||
/* turn on GPADC */
|
||||
if (use_gpadc) {
|
||||
ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1,
|
||||
PM8607_GPADC_EN, PM8607_GPADC_EN);
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit device_irq_init(struct pm860x_chip *chip,
|
||||
struct pm860x_platform_data *pdata)
|
||||
{
|
||||
struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
|
||||
: chip->companion;
|
||||
unsigned char status_buf[INT_STATUS_NUM];
|
||||
unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
|
||||
struct irq_desc *desc;
|
||||
int i, data, mask, ret = -EINVAL;
|
||||
int __irq;
|
||||
|
||||
if (!pdata || !pdata->irq_base) {
|
||||
dev_warn(chip->dev, "No interrupt support on IRQ base\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR
|
||||
| PM8607_B0_MISC1_INT_MASK;
|
||||
data = 0;
|
||||
chip->irq_mode = 0;
|
||||
if (pdata && pdata->irq_mode) {
|
||||
/*
|
||||
* irq_mode defines the way of clearing interrupt. If it's 1,
|
||||
* clear IRQ by write. Otherwise, clear it by read.
|
||||
* This control bit is valid from 88PM8607 B0 steping.
|
||||
*/
|
||||
data |= PM8607_B0_MISC1_INT_CLEAR;
|
||||
chip->irq_mode = 1;
|
||||
}
|
||||
ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* mask all IRQs */
|
||||
memset(status_buf, 0, INT_STATUS_NUM);
|
||||
ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1,
|
||||
INT_STATUS_NUM, status_buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (chip->irq_mode) {
|
||||
/* clear interrupt status by write */
|
||||
memset(status_buf, 0xFF, INT_STATUS_NUM);
|
||||
ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1,
|
||||
INT_STATUS_NUM, status_buf);
|
||||
} else {
|
||||
/* clear interrupt status by read */
|
||||
ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
|
||||
INT_STATUS_NUM, status_buf);
|
||||
}
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
mutex_init(&chip->irq_lock);
|
||||
chip->irq_base = pdata->irq_base;
|
||||
chip->core_irq = i2c->irq;
|
||||
if (!chip->core_irq)
|
||||
goto out;
|
||||
|
||||
desc = irq_to_desc(chip->core_irq);
|
||||
|
||||
/* register IRQ by genirq */
|
||||
for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) {
|
||||
__irq = i + chip->irq_base;
|
||||
set_irq_chip_data(__irq, chip);
|
||||
set_irq_chip_and_handler(__irq, &pm860x_irq_chip,
|
||||
handle_edge_irq);
|
||||
set_irq_nested_thread(__irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(__irq, IRQF_VALID);
|
||||
#else
|
||||
set_irq_noprobe(__irq);
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags,
|
||||
"88pm860x", chip);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Failed to request IRQ: %d\n", ret);
|
||||
chip->core_irq = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out:
|
||||
chip->core_irq = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __devexit device_irq_exit(struct pm860x_chip *chip)
|
||||
{
|
||||
if (chip->core_irq)
|
||||
free_irq(chip->core_irq, chip);
|
||||
}
|
||||
|
||||
static void __devinit device_8606_init(struct pm860x_chip *chip,
|
||||
struct i2c_client *i2c,
|
||||
struct pm860x_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (pdata && pdata->backlight) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0],
|
||||
ARRAY_SIZE(backlight_devs),
|
||||
&backlight_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add backlight "
|
||||
"subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata && pdata->led) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &led_devs[0],
|
||||
ARRAY_SIZE(led_devs),
|
||||
&led_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add led "
|
||||
"subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
return;
|
||||
out_dev:
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit(chip);
|
||||
}
|
||||
|
||||
static void __devinit device_8607_init(struct pm860x_chip *chip,
|
||||
struct i2c_client *i2c,
|
||||
struct pm860x_platform_data *pdata)
|
||||
{
|
||||
int data, ret;
|
||||
|
||||
ret = pm860x_reg_read(i2c, PM8607_CHIP_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if ((ret & PM8607_VERSION_MASK) == PM8607_VERSION)
|
||||
dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n",
|
||||
ret);
|
||||
else {
|
||||
dev_err(chip->dev, "Failed to detect Marvell 88PM8607. "
|
||||
"Chip ID: %02x\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = pm860x_reg_read(i2c, PM8607_BUCK3);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if (ret & PM8607_BUCK3_DOUBLE)
|
||||
chip->buck3_double = 1;
|
||||
|
||||
ret = pm860x_reg_read(i2c, PM8607_B0_MISC1);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pdata && (pdata->i2c_port == PI2C_PORT))
|
||||
data = PM8607_B0_MISC1_PI2C;
|
||||
else
|
||||
data = 0;
|
||||
ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to access MISC1:%d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = device_gpadc_init(chip, pdata);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = device_irq_init(chip, pdata);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0],
|
||||
ARRAY_SIZE(regulator_devs),
|
||||
®ulator_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add regulator subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
|
||||
if (pdata && pdata->touch) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &touch_devs[0],
|
||||
ARRAY_SIZE(touch_devs),
|
||||
&touch_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add touch "
|
||||
"subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata && pdata->power) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &power_devs[0],
|
||||
ARRAY_SIZE(power_devs),
|
||||
&power_supply_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add power supply "
|
||||
"subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
|
||||
ARRAY_SIZE(onkey_devs),
|
||||
&onkey_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add onkey subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
|
||||
return;
|
||||
out_dev:
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit(chip);
|
||||
out:
|
||||
return;
|
||||
}
|
||||
|
||||
int pm860x_device_init(struct pm860x_chip *chip,
|
||||
struct pm860x_platform_data *pdata)
|
||||
{
|
||||
chip->core_irq = 0;
|
||||
|
||||
switch (chip->id) {
|
||||
case CHIP_PM8606:
|
||||
device_8606_init(chip, chip->client, pdata);
|
||||
break;
|
||||
case CHIP_PM8607:
|
||||
device_8607_init(chip, chip->client, pdata);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chip->companion) {
|
||||
switch (chip->id) {
|
||||
case CHIP_PM8607:
|
||||
device_8606_init(chip, chip->companion, pdata);
|
||||
break;
|
||||
case CHIP_PM8606:
|
||||
device_8607_init(chip, chip->companion, pdata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pm860x_device_exit(struct pm860x_chip *chip)
|
||||
{
|
||||
device_irq_exit(chip);
|
||||
mfd_remove_devices(chip->dev);
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* I2C driver for Marvell 88PM860x
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
static inline int pm860x_read_device(struct i2c_client *i2c,
|
||||
int reg, int bytes, void *dest)
|
||||
{
|
||||
unsigned char data;
|
||||
int ret;
|
||||
|
||||
data = (unsigned char)reg;
|
||||
ret = i2c_master_send(i2c, &data, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_master_recv(i2c, dest, bytes);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pm860x_write_device(struct i2c_client *i2c,
|
||||
int reg, int bytes, void *src)
|
||||
{
|
||||
unsigned char buf[bytes + 1];
|
||||
int ret;
|
||||
|
||||
buf[0] = (unsigned char)reg;
|
||||
memcpy(&buf[1], src, bytes);
|
||||
|
||||
ret = i2c_master_send(i2c, buf, bytes + 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pm860x_reg_read(struct i2c_client *i2c, int reg)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(i2c);
|
||||
unsigned char data;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = pm860x_read_device(i2c, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return (int)data;
|
||||
}
|
||||
EXPORT_SYMBOL(pm860x_reg_read);
|
||||
|
||||
int pm860x_reg_write(struct i2c_client *i2c, int reg,
|
||||
unsigned char data)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = pm860x_write_device(i2c, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm860x_reg_write);
|
||||
|
||||
int pm860x_bulk_read(struct i2c_client *i2c, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = pm860x_read_device(i2c, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm860x_bulk_read);
|
||||
|
||||
int pm860x_bulk_write(struct i2c_client *i2c, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = pm860x_write_device(i2c, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm860x_bulk_write);
|
||||
|
||||
int pm860x_set_bits(struct i2c_client *i2c, int reg,
|
||||
unsigned char mask, unsigned char data)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(i2c);
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = pm860x_read_device(i2c, reg, 1, &value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
value &= ~mask;
|
||||
value |= data;
|
||||
ret = pm860x_write_device(i2c, reg, 1, &value);
|
||||
out:
|
||||
mutex_unlock(&chip->io_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pm860x_set_bits);
|
||||
|
||||
|
||||
static const struct i2c_device_id pm860x_id_table[] = {
|
||||
{ "88PM860x", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pm860x_id_table);
|
||||
|
||||
static int verify_addr(struct i2c_client *i2c)
|
||||
{
|
||||
unsigned short addr_8607[] = {0x30, 0x34};
|
||||
unsigned short addr_8606[] = {0x10, 0x11};
|
||||
int size, i;
|
||||
|
||||
if (i2c == NULL)
|
||||
return 0;
|
||||
size = ARRAY_SIZE(addr_8606);
|
||||
for (i = 0; i < size; i++) {
|
||||
if (i2c->addr == *(addr_8606 + i))
|
||||
return CHIP_PM8606;
|
||||
}
|
||||
size = ARRAY_SIZE(addr_8607);
|
||||
for (i = 0; i < size; i++) {
|
||||
if (i2c->addr == *(addr_8607 + i))
|
||||
return CHIP_PM8607;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit pm860x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct pm860x_platform_data *pdata = client->dev.platform_data;
|
||||
struct pm860x_chip *chip;
|
||||
|
||||
if (!pdata) {
|
||||
pr_info("No platform data in %s!\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL);
|
||||
if (chip == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->id = verify_addr(client);
|
||||
chip->client = client;
|
||||
i2c_set_clientdata(client, chip);
|
||||
chip->dev = &client->dev;
|
||||
mutex_init(&chip->io_lock);
|
||||
dev_set_drvdata(chip->dev, chip);
|
||||
|
||||
/*
|
||||
* Both client and companion client shares same platform driver.
|
||||
* Driver distinguishes them by pdata->companion_addr.
|
||||
* pdata->companion_addr is only assigned if companion chip exists.
|
||||
* At the same time, the companion_addr shouldn't equal to client
|
||||
* address.
|
||||
*/
|
||||
if (pdata->companion_addr && (pdata->companion_addr != client->addr)) {
|
||||
chip->companion_addr = pdata->companion_addr;
|
||||
chip->companion = i2c_new_dummy(chip->client->adapter,
|
||||
chip->companion_addr);
|
||||
i2c_set_clientdata(chip->companion, chip);
|
||||
}
|
||||
|
||||
pm860x_device_init(chip, pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pm860x_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pm860x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
pm860x_device_exit(chip);
|
||||
i2c_unregister_device(chip->companion);
|
||||
i2c_set_clientdata(chip->companion, NULL);
|
||||
i2c_set_clientdata(chip->client, NULL);
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pm860x_driver = {
|
||||
.driver = {
|
||||
.name = "88PM860x",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_probe,
|
||||
.remove = __devexit_p(pm860x_remove),
|
||||
.id_table = pm860x_id_table,
|
||||
};
|
||||
|
||||
static int __init pm860x_i2c_init(void)
|
||||
{
|
||||
int ret;
|
||||
ret = i2c_add_driver(&pm860x_driver);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to register 88PM860x I2C driver: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
subsys_initcall(pm860x_i2c_init);
|
||||
|
||||
static void __exit pm860x_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pm860x_driver);
|
||||
}
|
||||
module_exit(pm860x_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -9,6 +9,16 @@ config MFD_CORE
|
|||
tristate
|
||||
default n
|
||||
|
||||
config MFD_88PM860X
|
||||
bool "Support Marvell 88PM8606/88PM8607"
|
||||
depends on I2C=y
|
||||
select MFD_CORE
|
||||
help
|
||||
This supports for Marvell 88PM8606/88PM8607 Power Management IC.
|
||||
This includes the I2C driver and the core APIs _only_, you have to
|
||||
select individual components like voltage regulators, RTC and
|
||||
battery-charger under the corresponding menus.
|
||||
|
||||
config MFD_SM501
|
||||
tristate "Support for Silicon Motion SM501"
|
||||
---help---
|
||||
|
@ -37,7 +47,7 @@ config MFD_ASIC3
|
|||
|
||||
config MFD_SH_MOBILE_SDHI
|
||||
bool "Support for SuperH Mobile SDHI"
|
||||
depends on SUPERH
|
||||
depends on SUPERH || ARCH_SHMOBILE
|
||||
select MFD_CORE
|
||||
---help---
|
||||
This driver supports the SDHI hardware block found in many
|
||||
|
@ -68,6 +78,15 @@ config HTC_PASIC3
|
|||
HTC Magician devices, respectively. Actual functionality is
|
||||
handled by the leds-pasic3 and ds1wm drivers.
|
||||
|
||||
config HTC_I2CPLD
|
||||
bool "HTC I2C PLD chip support"
|
||||
depends on I2C=y && GPIOLIB
|
||||
help
|
||||
If you say yes here you get support for the supposed CPLD
|
||||
found on omap850 HTC devices like the HTC Wizard and HTC Herald.
|
||||
This device provides input and output GPIOs through an I2C
|
||||
interface to one or more sub-chips.
|
||||
|
||||
config UCB1400_CORE
|
||||
tristate "Philips UCB1400 Core driver"
|
||||
depends on AC97_BUS
|
||||
|
@ -184,6 +203,16 @@ config PMIC_ADP5520
|
|||
individual components like LCD backlight, LEDs, GPIOs and Kepad
|
||||
under the corresponding menus.
|
||||
|
||||
config MFD_MAX8925
|
||||
bool "Maxim Semiconductor MAX8925 PMIC Support"
|
||||
depends on I2C=y
|
||||
select MFD_CORE
|
||||
help
|
||||
Say yes here to support for Maxim Semiconductor MAX8925. This is
|
||||
a Power Management IC. This driver provies common support for
|
||||
accessing the device, additional drivers must be enabled in order
|
||||
to use the functionality of the device.
|
||||
|
||||
config MFD_WM8400
|
||||
tristate "Support Wolfson Microelectronics WM8400"
|
||||
select MFD_CORE
|
||||
|
@ -205,7 +234,7 @@ config MFD_WM831X
|
|||
functionality of the device.
|
||||
|
||||
config MFD_WM8350
|
||||
tristate
|
||||
bool
|
||||
|
||||
config MFD_WM8350_CONFIG_MODE_0
|
||||
bool
|
||||
|
@ -256,9 +285,9 @@ config MFD_WM8352_CONFIG_MODE_3
|
|||
depends on MFD_WM8350
|
||||
|
||||
config MFD_WM8350_I2C
|
||||
tristate "Support Wolfson Microelectronics WM8350 with I2C"
|
||||
bool "Support Wolfson Microelectronics WM8350 with I2C"
|
||||
select MFD_WM8350
|
||||
depends on I2C
|
||||
depends on I2C=y
|
||||
help
|
||||
The WM8350 is an integrated audio and power management
|
||||
subsystem with watchdog and RTC functionality for embedded
|
||||
|
@ -266,6 +295,18 @@ config MFD_WM8350_I2C
|
|||
I2C as the control interface. Additional options must be
|
||||
selected to enable support for the functionality of the chip.
|
||||
|
||||
config MFD_WM8994
|
||||
tristate "Support Wolfson Microelectronics WM8994"
|
||||
select MFD_CORE
|
||||
depends on I2C
|
||||
help
|
||||
The WM8994 is a highly integrated hi-fi CODEC designed for
|
||||
smartphone applicatiosn. As well as audio functionality it
|
||||
has on board GPIO and regulator functionality which is
|
||||
supported via the relevant subsystems. This driver provides
|
||||
core support for the WM8994, in order to use the actual
|
||||
functionaltiy of the device other drivers must be enabled.
|
||||
|
||||
config MFD_PCF50633
|
||||
tristate "Support for NXP PCF50633"
|
||||
depends on I2C
|
||||
|
@ -300,8 +341,8 @@ config PCF50633_GPIO
|
|||
the PCF50633 chip.
|
||||
|
||||
config AB3100_CORE
|
||||
tristate "ST-Ericsson AB3100 Mixed Signal Circuit core functions"
|
||||
depends on I2C
|
||||
bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions"
|
||||
depends on I2C=y
|
||||
default y if ARCH_U300
|
||||
help
|
||||
Select this to enable the AB3100 Mixed Signal IC core
|
||||
|
@ -329,16 +370,6 @@ config EZX_PCAP
|
|||
This enables the PCAP ASIC present on EZX Phones. This is
|
||||
needed for MMC, TouchScreen, Sound, USB, etc..
|
||||
|
||||
config MFD_88PM8607
|
||||
bool "Support Marvell 88PM8607"
|
||||
depends on I2C=y
|
||||
select MFD_CORE
|
||||
help
|
||||
This supports for Marvell 88PM8607 Power Management IC. This includes
|
||||
the I2C driver and the core APIs _only_, you have to select
|
||||
individual components like voltage regulators, RTC and
|
||||
battery-charger under the corresponding menus.
|
||||
|
||||
config AB4500_CORE
|
||||
tristate "ST-Ericsson's AB4500 Mixed Signal Power management chip"
|
||||
depends on SPI
|
||||
|
@ -358,6 +389,15 @@ config MFD_TIMBERDALE
|
|||
|
||||
The timberdale FPGA can be found on the Intel Atom development board
|
||||
for in-vehicle infontainment, called Russellville.
|
||||
|
||||
config LPC_SCH
|
||||
tristate "Intel SCH LPC"
|
||||
depends on PCI
|
||||
select MFD_CORE
|
||||
help
|
||||
LPC bridge function of the Intel SCH provides support for
|
||||
System Management Bus and General Purpose I/O.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Multimedia Capabilities Port drivers"
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
# Makefile for multifunction miscellaneous devices
|
||||
#
|
||||
|
||||
88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o
|
||||
obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o
|
||||
obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
|
||||
obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o
|
||||
|
||||
obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o
|
||||
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
|
||||
obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o
|
||||
|
||||
obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
|
||||
|
||||
|
@ -22,6 +25,7 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o
|
|||
wm8350-objs += wm8350-irq.o
|
||||
obj-$(CONFIG_MFD_WM8350) += wm8350.o
|
||||
obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o
|
||||
obj-$(CONFIG_MFD_WM8994) += wm8994-core.o
|
||||
|
||||
obj-$(CONFIG_TPS65010) += tps65010.o
|
||||
obj-$(CONFIG_MENELAUS) += menelaus.o
|
||||
|
@ -47,6 +51,8 @@ endif
|
|||
obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
|
||||
|
||||
obj-$(CONFIG_PMIC_DA903X) += da903x.o
|
||||
max8925-objs := max8925-core.o max8925-i2c.o
|
||||
obj-$(CONFIG_MFD_MAX8925) += max8925.o
|
||||
|
||||
obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
|
||||
obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o
|
||||
|
@ -55,5 +61,5 @@ obj-$(CONFIG_AB3100_CORE) += ab3100-core.o
|
|||
obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o
|
||||
obj-$(CONFIG_AB4500_CORE) += ab4500-core.o
|
||||
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
|
||||
obj-$(CONFIG_MFD_88PM8607) += 88pm8607.o
|
||||
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
|
||||
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2007-2009 ST-Ericsson
|
||||
* Copyright (C) 2007-2010 ST-Ericsson
|
||||
* License terms: GNU General Public License (GPL) version 2
|
||||
* Low-level core for exclusive access to the AB3100 IC on the I2C bus
|
||||
* and some basic chip-configuration.
|
||||
|
@ -14,6 +14,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
@ -365,18 +366,23 @@ int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100,
|
|||
}
|
||||
EXPORT_SYMBOL(ab3100_event_registers_startup_state_get);
|
||||
|
||||
/* Interrupt handling worker */
|
||||
static void ab3100_work(struct work_struct *work)
|
||||
/*
|
||||
* This is a threaded interrupt handler so we can make some
|
||||
* I2C calls etc.
|
||||
*/
|
||||
static irqreturn_t ab3100_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct ab3100 *ab3100 = container_of(work, struct ab3100, work);
|
||||
struct ab3100 *ab3100 = data;
|
||||
u8 event_regs[3];
|
||||
u32 fatevent;
|
||||
int err;
|
||||
|
||||
add_interrupt_randomness(irq);
|
||||
|
||||
err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1,
|
||||
event_regs, 3);
|
||||
if (err)
|
||||
goto err_event_wq;
|
||||
goto err_event;
|
||||
|
||||
fatevent = (event_regs[0] << 16) |
|
||||
(event_regs[1] << 8) |
|
||||
|
@ -398,29 +404,11 @@ static void ab3100_work(struct work_struct *work)
|
|||
dev_dbg(ab3100->dev,
|
||||
"IRQ Event: 0x%08x\n", fatevent);
|
||||
|
||||
/* By now the IRQ should be acked and deasserted so enable it again */
|
||||
enable_irq(ab3100->i2c_client->irq);
|
||||
return;
|
||||
return IRQ_HANDLED;
|
||||
|
||||
err_event_wq:
|
||||
err_event:
|
||||
dev_dbg(ab3100->dev,
|
||||
"error in event workqueue\n");
|
||||
/* Enable the IRQ anyway, what choice do we have? */
|
||||
enable_irq(ab3100->i2c_client->irq);
|
||||
return;
|
||||
}
|
||||
|
||||
static irqreturn_t ab3100_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct ab3100 *ab3100 = data;
|
||||
/*
|
||||
* Disable the IRQ and dispatch a worker to handle the
|
||||
* event. Since the chip resides on I2C this is slow
|
||||
* stuff and we will re-enable the interrupts once th
|
||||
* worker has finished.
|
||||
*/
|
||||
disable_irq_nosync(irq);
|
||||
schedule_work(&ab3100->work);
|
||||
"error reading event status\n");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
@ -735,10 +723,7 @@ static struct platform_device ab3100_##devname##_device = { \
|
|||
.id = -1, \
|
||||
}
|
||||
|
||||
/*
|
||||
* This lists all the subdevices and corresponding register
|
||||
* ranges.
|
||||
*/
|
||||
/* This lists all the subdevices */
|
||||
AB3100_DEVICE(dac, "ab3100-dac");
|
||||
AB3100_DEVICE(leds, "ab3100-leds");
|
||||
AB3100_DEVICE(power, "ab3100-power");
|
||||
|
@ -904,12 +889,11 @@ static int __init ab3100_probe(struct i2c_client *client,
|
|||
if (err)
|
||||
goto exit_no_setup;
|
||||
|
||||
INIT_WORK(&ab3100->work, ab3100_work);
|
||||
|
||||
err = request_threaded_irq(client->irq, NULL, ab3100_irq_handler,
|
||||
IRQF_ONESHOT, "ab3100-core", ab3100);
|
||||
/* This real unpredictable IRQ is of course sampled for entropy */
|
||||
err = request_irq(client->irq, ab3100_irq_handler,
|
||||
IRQF_DISABLED | IRQF_SAMPLE_RANDOM,
|
||||
"AB3100 IRQ", ab3100);
|
||||
rand_initialize_irq(client->irq);
|
||||
|
||||
if (err)
|
||||
goto exit_no_irq;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/ab3100.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
/* The OTP registers */
|
||||
#define AB3100_OTP0 0xb0
|
||||
|
@ -95,11 +96,10 @@ static int __init ab3100_otp_read(struct ab3100_otp *otp)
|
|||
* This is a simple debugfs human-readable file that dumps out
|
||||
* the contents of the OTP.
|
||||
*/
|
||||
#ifdef CONFIG_DEBUGFS
|
||||
static int show_otp(struct seq_file *s, void *v)
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int ab3100_show_otp(struct seq_file *s, void *v)
|
||||
{
|
||||
struct ab3100_otp *otp = s->private;
|
||||
int err;
|
||||
|
||||
seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED");
|
||||
seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq);
|
||||
|
@ -113,7 +113,7 @@ static int show_otp(struct seq_file *s, void *v)
|
|||
|
||||
static int ab3100_otp_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, ab3100_otp_show, inode->i_private);
|
||||
return single_open(file, ab3100_show_otp, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations ab3100_otp_operations = {
|
||||
|
@ -131,13 +131,14 @@ static int __init ab3100_otp_init_debugfs(struct device *dev,
|
|||
&ab3100_otp_operations);
|
||||
if (!otp->debugfs) {
|
||||
dev_err(dev, "AB3100 debugfs OTP file registration failed!\n");
|
||||
return err;
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp)
|
||||
{
|
||||
debugfs_remove_file(otp->debugfs);
|
||||
debugfs_remove(otp->debugfs);
|
||||
}
|
||||
#else
|
||||
/* Compile this out if debugfs not selected */
|
||||
|
|
|
@ -0,0 +1,710 @@
|
|||
/*
|
||||
* htc-i2cpld.c
|
||||
* Chip driver for an unknown CPLD chip found on omap850 HTC devices like
|
||||
* the HTC Wizard and HTC Herald.
|
||||
* The cpld is located on the i2c bus and acts as an input/output GPIO
|
||||
* extender.
|
||||
*
|
||||
* Copyright (C) 2009 Cory Maccarrone <darkstar6262@gmail.com>
|
||||
*
|
||||
* Based on work done in the linwizard project
|
||||
* Copyright (C) 2008-2009 Angelo Arrifano <miknix@gmail.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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/htcpld.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
struct htcpld_chip {
|
||||
spinlock_t lock;
|
||||
|
||||
/* chip info */
|
||||
u8 reset;
|
||||
u8 addr;
|
||||
struct device *dev;
|
||||
struct i2c_client *client;
|
||||
|
||||
/* Output details */
|
||||
u8 cache_out;
|
||||
struct gpio_chip chip_out;
|
||||
|
||||
/* Input details */
|
||||
u8 cache_in;
|
||||
struct gpio_chip chip_in;
|
||||
|
||||
u16 irqs_enabled;
|
||||
uint irq_start;
|
||||
int nirqs;
|
||||
|
||||
/*
|
||||
* Work structure to allow for setting values outside of any
|
||||
* possible interrupt context
|
||||
*/
|
||||
struct work_struct set_val_work;
|
||||
};
|
||||
|
||||
struct htcpld_data {
|
||||
/* irq info */
|
||||
u16 irqs_enabled;
|
||||
uint irq_start;
|
||||
int nirqs;
|
||||
uint chained_irq;
|
||||
unsigned int int_reset_gpio_hi;
|
||||
unsigned int int_reset_gpio_lo;
|
||||
|
||||
/* htcpld info */
|
||||
struct htcpld_chip *chip;
|
||||
unsigned int nchips;
|
||||
};
|
||||
|
||||
/* There does not appear to be a way to proactively mask interrupts
|
||||
* on the htcpld chip itself. So, we simply ignore interrupts that
|
||||
* aren't desired. */
|
||||
static void htcpld_mask(unsigned int irq)
|
||||
{
|
||||
struct htcpld_chip *chip = get_irq_chip_data(irq);
|
||||
chip->irqs_enabled &= ~(1 << (irq - chip->irq_start));
|
||||
pr_debug("HTCPLD mask %d %04x\n", irq, chip->irqs_enabled);
|
||||
}
|
||||
static void htcpld_unmask(unsigned int irq)
|
||||
{
|
||||
struct htcpld_chip *chip = get_irq_chip_data(irq);
|
||||
chip->irqs_enabled |= 1 << (irq - chip->irq_start);
|
||||
pr_debug("HTCPLD unmask %d %04x\n", irq, chip->irqs_enabled);
|
||||
}
|
||||
|
||||
static int htcpld_set_type(unsigned int irq, unsigned int flags)
|
||||
{
|
||||
struct irq_desc *d = irq_to_desc(irq);
|
||||
|
||||
if (!d) {
|
||||
pr_err("HTCPLD invalid IRQ: %d\n", irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (flags & ~IRQ_TYPE_SENSE_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
/* We only allow edge triggering */
|
||||
if (flags & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))
|
||||
return -EINVAL;
|
||||
|
||||
d->status &= ~IRQ_TYPE_SENSE_MASK;
|
||||
d->status |= flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip htcpld_muxed_chip = {
|
||||
.name = "htcpld",
|
||||
.mask = htcpld_mask,
|
||||
.unmask = htcpld_unmask,
|
||||
.set_type = htcpld_set_type,
|
||||
};
|
||||
|
||||
/* To properly dispatch IRQ events, we need to read from the
|
||||
* chip. This is an I2C action that could possibly sleep
|
||||
* (which is bad in interrupt context) -- so we use a threaded
|
||||
* interrupt handler to get around that.
|
||||
*/
|
||||
static irqreturn_t htcpld_handler(int irq, void *dev)
|
||||
{
|
||||
struct htcpld_data *htcpld = dev;
|
||||
unsigned int i;
|
||||
unsigned long flags;
|
||||
int irqpin;
|
||||
struct irq_desc *desc;
|
||||
|
||||
if (!htcpld) {
|
||||
pr_debug("htcpld is null in ISR\n");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each chip, do a read of the chip and trigger any interrupts
|
||||
* desired. The interrupts will be triggered from LSB to MSB (i.e.
|
||||
* bit 0 first, then bit 1, etc.)
|
||||
*
|
||||
* For chips that have no interrupt range specified, just skip 'em.
|
||||
*/
|
||||
for (i = 0; i < htcpld->nchips; i++) {
|
||||
struct htcpld_chip *chip = &htcpld->chip[i];
|
||||
struct i2c_client *client;
|
||||
int val;
|
||||
unsigned long uval, old_val;
|
||||
|
||||
if (!chip) {
|
||||
pr_debug("chip %d is null in ISR\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chip->nirqs == 0)
|
||||
continue;
|
||||
|
||||
client = chip->client;
|
||||
if (!client) {
|
||||
pr_debug("client %d is null in ISR\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Scan the chip */
|
||||
val = i2c_smbus_read_byte_data(client, chip->cache_out);
|
||||
if (val < 0) {
|
||||
/* Throw a warning and skip this chip */
|
||||
dev_warn(chip->dev, "Unable to read from chip: %d\n",
|
||||
val);
|
||||
continue;
|
||||
}
|
||||
|
||||
uval = (unsigned long)val;
|
||||
|
||||
spin_lock_irqsave(&chip->lock, flags);
|
||||
|
||||
/* Save away the old value so we can compare it */
|
||||
old_val = chip->cache_in;
|
||||
|
||||
/* Write the new value */
|
||||
chip->cache_in = uval;
|
||||
|
||||
spin_unlock_irqrestore(&chip->lock, flags);
|
||||
|
||||
/*
|
||||
* For each bit in the data (starting at bit 0), trigger
|
||||
* associated interrupts.
|
||||
*/
|
||||
for (irqpin = 0; irqpin < chip->nirqs; irqpin++) {
|
||||
unsigned oldb, newb;
|
||||
int flags;
|
||||
|
||||
irq = chip->irq_start + irqpin;
|
||||
desc = irq_to_desc(irq);
|
||||
flags = desc->status;
|
||||
|
||||
/* Run the IRQ handler, but only if the bit value
|
||||
* changed, and the proper flags are set */
|
||||
oldb = (old_val >> irqpin) & 1;
|
||||
newb = (uval >> irqpin) & 1;
|
||||
|
||||
if ((!oldb && newb && (flags & IRQ_TYPE_EDGE_RISING)) ||
|
||||
(oldb && !newb &&
|
||||
(flags & IRQ_TYPE_EDGE_FALLING))) {
|
||||
pr_debug("fire IRQ %d\n", irqpin);
|
||||
desc->handle_irq(irq, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In order to continue receiving interrupts, the int_reset_gpio must
|
||||
* be asserted.
|
||||
*/
|
||||
if (htcpld->int_reset_gpio_hi)
|
||||
gpio_set_value(htcpld->int_reset_gpio_hi, 1);
|
||||
if (htcpld->int_reset_gpio_lo)
|
||||
gpio_set_value(htcpld->int_reset_gpio_lo, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* The GPIO set routines can be called from interrupt context, especially if,
|
||||
* for example they're attached to the led-gpio framework and a trigger is
|
||||
* enabled. As such, we declared work above in the htcpld_chip structure,
|
||||
* and that work is scheduled in the set routine. The kernel can then run
|
||||
* the I2C functions, which will sleep, in process context.
|
||||
*/
|
||||
void htcpld_chip_set(struct gpio_chip *chip, unsigned offset, int val)
|
||||
{
|
||||
struct i2c_client *client;
|
||||
struct htcpld_chip *chip_data;
|
||||
unsigned long flags;
|
||||
|
||||
chip_data = container_of(chip, struct htcpld_chip, chip_out);
|
||||
if (!chip_data)
|
||||
return;
|
||||
|
||||
client = chip_data->client;
|
||||
if (client == NULL)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&chip_data->lock, flags);
|
||||
if (val)
|
||||
chip_data->cache_out |= (1 << offset);
|
||||
else
|
||||
chip_data->cache_out &= ~(1 << offset);
|
||||
spin_unlock_irqrestore(&chip_data->lock, flags);
|
||||
|
||||
schedule_work(&(chip_data->set_val_work));
|
||||
}
|
||||
|
||||
void htcpld_chip_set_ni(struct work_struct *work)
|
||||
{
|
||||
struct htcpld_chip *chip_data;
|
||||
struct i2c_client *client;
|
||||
|
||||
chip_data = container_of(work, struct htcpld_chip, set_val_work);
|
||||
client = chip_data->client;
|
||||
i2c_smbus_read_byte_data(client, chip_data->cache_out);
|
||||
}
|
||||
|
||||
int htcpld_chip_get(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct htcpld_chip *chip_data;
|
||||
int val = 0;
|
||||
int is_input = 0;
|
||||
|
||||
/* Try out first */
|
||||
chip_data = container_of(chip, struct htcpld_chip, chip_out);
|
||||
if (!chip_data) {
|
||||
/* Try in */
|
||||
is_input = 1;
|
||||
chip_data = container_of(chip, struct htcpld_chip, chip_in);
|
||||
if (!chip_data)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Determine if this is an input or output GPIO */
|
||||
if (!is_input)
|
||||
/* Use the output cache */
|
||||
val = (chip_data->cache_out >> offset) & 1;
|
||||
else
|
||||
/* Use the input cache */
|
||||
val = (chip_data->cache_in >> offset) & 1;
|
||||
|
||||
if (val)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int htcpld_direction_output(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
htcpld_chip_set(chip, offset, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int htcpld_direction_input(struct gpio_chip *chip,
|
||||
unsigned offset)
|
||||
{
|
||||
/*
|
||||
* No-op: this function can only be called on the input chip.
|
||||
* We do however make sure the offset is within range.
|
||||
*/
|
||||
return (offset < chip->ngpio) ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
int htcpld_chip_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct htcpld_chip *chip_data;
|
||||
|
||||
chip_data = container_of(chip, struct htcpld_chip, chip_in);
|
||||
|
||||
if (offset < chip_data->nirqs)
|
||||
return chip_data->irq_start + offset;
|
||||
else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void htcpld_chip_reset(struct i2c_client *client)
|
||||
{
|
||||
struct htcpld_chip *chip_data = i2c_get_clientdata(client);
|
||||
if (!chip_data)
|
||||
return;
|
||||
|
||||
i2c_smbus_read_byte_data(
|
||||
client, (chip_data->cache_out = chip_data->reset));
|
||||
}
|
||||
|
||||
static int __devinit htcpld_setup_chip_irq(
|
||||
struct platform_device *pdev,
|
||||
int chip_index)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct htcpld_core_platform_data *pdata;
|
||||
struct htcpld_chip *chip;
|
||||
struct htcpld_chip_platform_data *plat_chip_data;
|
||||
unsigned int irq, irq_end;
|
||||
int ret = 0;
|
||||
|
||||
/* Get the platform and driver data */
|
||||
pdata = dev->platform_data;
|
||||
htcpld = platform_get_drvdata(pdev);
|
||||
chip = &htcpld->chip[chip_index];
|
||||
plat_chip_data = &pdata->chip[chip_index];
|
||||
|
||||
/* Setup irq handlers */
|
||||
irq_end = chip->irq_start + chip->nirqs;
|
||||
for (irq = chip->irq_start; irq < irq_end; irq++) {
|
||||
set_irq_chip(irq, &htcpld_muxed_chip);
|
||||
set_irq_chip_data(irq, chip);
|
||||
set_irq_handler(irq, handle_simple_irq);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
|
||||
#else
|
||||
set_irq_probe(irq);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit htcpld_register_chip_i2c(
|
||||
struct platform_device *pdev,
|
||||
int chip_index)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct htcpld_core_platform_data *pdata;
|
||||
struct htcpld_chip *chip;
|
||||
struct htcpld_chip_platform_data *plat_chip_data;
|
||||
struct i2c_adapter *adapter;
|
||||
struct i2c_client *client;
|
||||
struct i2c_board_info info;
|
||||
|
||||
/* Get the platform and driver data */
|
||||
pdata = dev->platform_data;
|
||||
htcpld = platform_get_drvdata(pdev);
|
||||
chip = &htcpld->chip[chip_index];
|
||||
plat_chip_data = &pdata->chip[chip_index];
|
||||
|
||||
adapter = i2c_get_adapter(pdata->i2c_adapter_id);
|
||||
if (adapter == NULL) {
|
||||
/* Eek, no such I2C adapter! Bail out. */
|
||||
dev_warn(dev, "Chip at i2c address 0x%x: Invalid i2c adapter %d\n",
|
||||
plat_chip_data->addr, pdata->i2c_adapter_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
|
||||
dev_warn(dev, "i2c adapter %d non-functional\n",
|
||||
pdata->i2c_adapter_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(struct i2c_board_info));
|
||||
info.addr = plat_chip_data->addr;
|
||||
strlcpy(info.type, "htcpld-chip", I2C_NAME_SIZE);
|
||||
info.platform_data = chip;
|
||||
|
||||
/* Add the I2C device. This calls the probe() function. */
|
||||
client = i2c_new_device(adapter, &info);
|
||||
if (!client) {
|
||||
/* I2C device registration failed, contineu with the next */
|
||||
dev_warn(dev, "Unable to add I2C device for 0x%x\n",
|
||||
plat_chip_data->addr);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, chip);
|
||||
snprintf(client->name, I2C_NAME_SIZE, "Chip_0x%d", client->addr);
|
||||
chip->client = client;
|
||||
|
||||
/* Reset the chip */
|
||||
htcpld_chip_reset(client);
|
||||
chip->cache_in = i2c_smbus_read_byte_data(client, chip->cache_out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __devinit htcpld_unregister_chip_i2c(
|
||||
struct platform_device *pdev,
|
||||
int chip_index)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct htcpld_chip *chip;
|
||||
|
||||
/* Get the platform and driver data */
|
||||
htcpld = platform_get_drvdata(pdev);
|
||||
chip = &htcpld->chip[chip_index];
|
||||
|
||||
if (chip->client)
|
||||
i2c_unregister_device(chip->client);
|
||||
}
|
||||
|
||||
static int __devinit htcpld_register_chip_gpio(
|
||||
struct platform_device *pdev,
|
||||
int chip_index)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct htcpld_core_platform_data *pdata;
|
||||
struct htcpld_chip *chip;
|
||||
struct htcpld_chip_platform_data *plat_chip_data;
|
||||
struct gpio_chip *gpio_chip;
|
||||
int ret = 0;
|
||||
|
||||
/* Get the platform and driver data */
|
||||
pdata = dev->platform_data;
|
||||
htcpld = platform_get_drvdata(pdev);
|
||||
chip = &htcpld->chip[chip_index];
|
||||
plat_chip_data = &pdata->chip[chip_index];
|
||||
|
||||
/* Setup the GPIO chips */
|
||||
gpio_chip = &(chip->chip_out);
|
||||
gpio_chip->label = "htcpld-out";
|
||||
gpio_chip->dev = dev;
|
||||
gpio_chip->owner = THIS_MODULE;
|
||||
gpio_chip->get = htcpld_chip_get;
|
||||
gpio_chip->set = htcpld_chip_set;
|
||||
gpio_chip->direction_input = NULL;
|
||||
gpio_chip->direction_output = htcpld_direction_output;
|
||||
gpio_chip->base = plat_chip_data->gpio_out_base;
|
||||
gpio_chip->ngpio = plat_chip_data->num_gpios;
|
||||
|
||||
gpio_chip = &(chip->chip_in);
|
||||
gpio_chip->label = "htcpld-in";
|
||||
gpio_chip->dev = dev;
|
||||
gpio_chip->owner = THIS_MODULE;
|
||||
gpio_chip->get = htcpld_chip_get;
|
||||
gpio_chip->set = NULL;
|
||||
gpio_chip->direction_input = htcpld_direction_input;
|
||||
gpio_chip->direction_output = NULL;
|
||||
gpio_chip->to_irq = htcpld_chip_to_irq;
|
||||
gpio_chip->base = plat_chip_data->gpio_in_base;
|
||||
gpio_chip->ngpio = plat_chip_data->num_gpios;
|
||||
|
||||
/* Add the GPIO chips */
|
||||
ret = gpiochip_add(&(chip->chip_out));
|
||||
if (ret) {
|
||||
dev_warn(dev, "Unable to register output GPIOs for 0x%x: %d\n",
|
||||
plat_chip_data->addr, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpiochip_add(&(chip->chip_in));
|
||||
if (ret) {
|
||||
int error;
|
||||
|
||||
dev_warn(dev, "Unable to register input GPIOs for 0x%x: %d\n",
|
||||
plat_chip_data->addr, ret);
|
||||
|
||||
error = gpiochip_remove(&(chip->chip_out));
|
||||
if (error)
|
||||
dev_warn(dev, "Error while trying to unregister gpio chip: %d\n", error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit htcpld_setup_chips(struct platform_device *pdev)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct htcpld_core_platform_data *pdata;
|
||||
int i;
|
||||
|
||||
/* Get the platform and driver data */
|
||||
pdata = dev->platform_data;
|
||||
htcpld = platform_get_drvdata(pdev);
|
||||
|
||||
/* Setup each chip's output GPIOs */
|
||||
htcpld->nchips = pdata->num_chip;
|
||||
htcpld->chip = kzalloc(sizeof(struct htcpld_chip) * htcpld->nchips,
|
||||
GFP_KERNEL);
|
||||
if (!htcpld->chip) {
|
||||
dev_warn(dev, "Unable to allocate memory for chips\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Add the chips as best we can */
|
||||
for (i = 0; i < htcpld->nchips; i++) {
|
||||
int ret;
|
||||
|
||||
/* Setup the HTCPLD chips */
|
||||
htcpld->chip[i].reset = pdata->chip[i].reset;
|
||||
htcpld->chip[i].cache_out = pdata->chip[i].reset;
|
||||
htcpld->chip[i].cache_in = 0;
|
||||
htcpld->chip[i].dev = dev;
|
||||
htcpld->chip[i].irq_start = pdata->chip[i].irq_base;
|
||||
htcpld->chip[i].nirqs = pdata->chip[i].num_irqs;
|
||||
|
||||
INIT_WORK(&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni);
|
||||
spin_lock_init(&(htcpld->chip[i].lock));
|
||||
|
||||
/* Setup the interrupts for the chip */
|
||||
if (htcpld->chained_irq) {
|
||||
ret = htcpld_setup_chip_irq(pdev, i);
|
||||
if (ret)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Register the chip with I2C */
|
||||
ret = htcpld_register_chip_i2c(pdev, i);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
|
||||
/* Register the chips with the GPIO subsystem */
|
||||
ret = htcpld_register_chip_gpio(pdev, i);
|
||||
if (ret) {
|
||||
/* Unregister the chip from i2c and continue */
|
||||
htcpld_unregister_chip_i2c(pdev, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
dev_info(dev, "Registered chip at 0x%x\n", pdata->chip[i].addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit htcpld_core_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct htcpld_data *htcpld;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct htcpld_core_platform_data *pdata;
|
||||
struct resource *res;
|
||||
int ret = 0;
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
pdata = dev->platform_data;
|
||||
if (!pdata) {
|
||||
dev_warn(dev, "Platform data not found for htcpld core!\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
htcpld = kzalloc(sizeof(struct htcpld_data), GFP_KERNEL);
|
||||
if (!htcpld)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Find chained irq */
|
||||
ret = -EINVAL;
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (res) {
|
||||
int flags;
|
||||
htcpld->chained_irq = res->start;
|
||||
|
||||
/* Setup the chained interrupt handler */
|
||||
flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
|
||||
ret = request_threaded_irq(htcpld->chained_irq,
|
||||
NULL, htcpld_handler,
|
||||
flags, pdev->name, htcpld);
|
||||
if (ret) {
|
||||
dev_warn(dev, "Unable to setup chained irq handler: %d\n", ret);
|
||||
goto fail;
|
||||
} else
|
||||
device_init_wakeup(dev, 0);
|
||||
}
|
||||
|
||||
/* Set the driver data */
|
||||
platform_set_drvdata(pdev, htcpld);
|
||||
|
||||
/* Setup the htcpld chips */
|
||||
ret = htcpld_setup_chips(pdev);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* Request the GPIO(s) for the int reset and set them up */
|
||||
if (pdata->int_reset_gpio_hi) {
|
||||
ret = gpio_request(pdata->int_reset_gpio_hi, "htcpld-core");
|
||||
if (ret) {
|
||||
/*
|
||||
* If it failed, that sucks, but we can probably
|
||||
* continue on without it.
|
||||
*/
|
||||
dev_warn(dev, "Unable to request int_reset_gpio_hi -- interrupts may not work\n");
|
||||
htcpld->int_reset_gpio_hi = 0;
|
||||
} else {
|
||||
htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi;
|
||||
gpio_set_value(htcpld->int_reset_gpio_hi, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->int_reset_gpio_lo) {
|
||||
ret = gpio_request(pdata->int_reset_gpio_lo, "htcpld-core");
|
||||
if (ret) {
|
||||
/*
|
||||
* If it failed, that sucks, but we can probably
|
||||
* continue on without it.
|
||||
*/
|
||||
dev_warn(dev, "Unable to request int_reset_gpio_lo -- interrupts may not work\n");
|
||||
htcpld->int_reset_gpio_lo = 0;
|
||||
} else {
|
||||
htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo;
|
||||
gpio_set_value(htcpld->int_reset_gpio_lo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(dev, "Initialized successfully\n");
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree(htcpld);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* The I2C Driver -- used internally */
|
||||
static const struct i2c_device_id htcpld_chip_id[] = {
|
||||
{ "htcpld-chip", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, htcpld_chip_id);
|
||||
|
||||
|
||||
static struct i2c_driver htcpld_chip_driver = {
|
||||
.driver = {
|
||||
.name = "htcpld-chip",
|
||||
},
|
||||
.id_table = htcpld_chip_id,
|
||||
};
|
||||
|
||||
/* The Core Driver */
|
||||
static struct platform_driver htcpld_core_driver = {
|
||||
.driver = {
|
||||
.name = "i2c-htcpld",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init htcpld_core_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Register the I2C Chip driver */
|
||||
ret = i2c_add_driver(&htcpld_chip_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Probe for our chips */
|
||||
return platform_driver_probe(&htcpld_core_driver, htcpld_core_probe);
|
||||
}
|
||||
|
||||
static void __exit htcpld_core_exit(void)
|
||||
{
|
||||
i2c_del_driver(&htcpld_chip_driver);
|
||||
platform_driver_unregister(&htcpld_core_driver);
|
||||
}
|
||||
|
||||
module_init(htcpld_core_init);
|
||||
module_exit(htcpld_core_exit);
|
||||
|
||||
MODULE_AUTHOR("Cory Maccarrone <darkstar6262@gmail.com>");
|
||||
MODULE_DESCRIPTION("I2C HTC PLD Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* lpc_sch.c - LPC interface for Intel Poulsbo SCH
|
||||
*
|
||||
* LPC bridge function of the Intel SCH contains many other
|
||||
* functional units, such as Interrupt controllers, Timers,
|
||||
* Power Management, System Management, GPIO, RTC, and LPC
|
||||
* Configuration Registers.
|
||||
*
|
||||
* Copyright (c) 2010 CompuLab Ltd
|
||||
* Author: Denis Turischev <denis@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 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/mfd/core.h>
|
||||
|
||||
#define SMBASE 0x40
|
||||
#define SMBUS_IO_SIZE 64
|
||||
|
||||
#define GPIOBASE 0x44
|
||||
#define GPIO_IO_SIZE 64
|
||||
|
||||
static struct resource smbus_sch_resource = {
|
||||
.flags = IORESOURCE_IO,
|
||||
};
|
||||
|
||||
|
||||
static struct resource gpio_sch_resource = {
|
||||
.flags = IORESOURCE_IO,
|
||||
};
|
||||
|
||||
static struct mfd_cell lpc_sch_cells[] = {
|
||||
{
|
||||
.name = "isch_smbus",
|
||||
.num_resources = 1,
|
||||
.resources = &smbus_sch_resource,
|
||||
},
|
||||
{
|
||||
.name = "sch_gpio",
|
||||
.num_resources = 1,
|
||||
.resources = &gpio_sch_resource,
|
||||
},
|
||||
};
|
||||
|
||||
static struct pci_device_id lpc_sch_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) },
|
||||
{ 0, }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, lpc_sch_ids);
|
||||
|
||||
static int __devinit lpc_sch_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
unsigned int base_addr_cfg;
|
||||
unsigned short base_addr;
|
||||
|
||||
pci_read_config_dword(dev, SMBASE, &base_addr_cfg);
|
||||
if (!(base_addr_cfg & (1 << 31))) {
|
||||
dev_err(&dev->dev, "Decode of the SMBus I/O range disabled\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
base_addr = (unsigned short)base_addr_cfg;
|
||||
if (base_addr == 0) {
|
||||
dev_err(&dev->dev, "I/O space for SMBus uninitialized\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
smbus_sch_resource.start = base_addr;
|
||||
smbus_sch_resource.end = base_addr + SMBUS_IO_SIZE - 1;
|
||||
|
||||
pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
|
||||
if (!(base_addr_cfg & (1 << 31))) {
|
||||
dev_err(&dev->dev, "Decode of the GPIO I/O range disabled\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
base_addr = (unsigned short)base_addr_cfg;
|
||||
if (base_addr == 0) {
|
||||
dev_err(&dev->dev, "I/O space for GPIO uninitialized\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gpio_sch_resource.start = base_addr;
|
||||
gpio_sch_resource.end = base_addr + GPIO_IO_SIZE - 1;
|
||||
|
||||
return mfd_add_devices(&dev->dev, -1,
|
||||
lpc_sch_cells, ARRAY_SIZE(lpc_sch_cells), NULL, 0);
|
||||
}
|
||||
|
||||
static void __devexit lpc_sch_remove(struct pci_dev *dev)
|
||||
{
|
||||
mfd_remove_devices(&dev->dev);
|
||||
}
|
||||
|
||||
static struct pci_driver lpc_sch_driver = {
|
||||
.name = "lpc_sch",
|
||||
.id_table = lpc_sch_ids,
|
||||
.probe = lpc_sch_probe,
|
||||
.remove = __devexit_p(lpc_sch_remove),
|
||||
};
|
||||
|
||||
static int __init lpc_sch_init(void)
|
||||
{
|
||||
return pci_register_driver(&lpc_sch_driver);
|
||||
}
|
||||
|
||||
static void __exit lpc_sch_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&lpc_sch_driver);
|
||||
}
|
||||
|
||||
module_init(lpc_sch_init);
|
||||
module_exit(lpc_sch_exit);
|
||||
|
||||
MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
|
||||
MODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,656 @@
|
|||
/*
|
||||
* Base driver for Maxim MAX8925
|
||||
*
|
||||
* Copyright (C) 2009-2010 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
static struct resource backlight_resources[] = {
|
||||
{
|
||||
.name = "max8925-backlight",
|
||||
.start = MAX8925_WLED_MODE_CNTL,
|
||||
.end = MAX8925_WLED_CNTL,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell backlight_devs[] = {
|
||||
{
|
||||
.name = "max8925-backlight",
|
||||
.num_resources = 1,
|
||||
.resources = &backlight_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource touch_resources[] = {
|
||||
{
|
||||
.name = "max8925-tsc",
|
||||
.start = MAX8925_TSC_IRQ,
|
||||
.end = MAX8925_ADC_RES_END,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell touch_devs[] = {
|
||||
{
|
||||
.name = "max8925-touch",
|
||||
.num_resources = 1,
|
||||
.resources = &touch_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource power_supply_resources[] = {
|
||||
{
|
||||
.name = "max8925-power",
|
||||
.start = MAX8925_CHG_IRQ1,
|
||||
.end = MAX8925_CHG_IRQ1_MASK,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell power_devs[] = {
|
||||
{
|
||||
.name = "max8925-power",
|
||||
.num_resources = 1,
|
||||
.resources = &power_supply_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource rtc_resources[] = {
|
||||
{
|
||||
.name = "max8925-rtc",
|
||||
.start = MAX8925_RTC_IRQ,
|
||||
.end = MAX8925_RTC_IRQ_MASK,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell rtc_devs[] = {
|
||||
{
|
||||
.name = "max8925-rtc",
|
||||
.num_resources = 1,
|
||||
.resources = &rtc_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
#define MAX8925_REG_RESOURCE(_start, _end) \
|
||||
{ \
|
||||
.start = MAX8925_##_start, \
|
||||
.end = MAX8925_##_end, \
|
||||
.flags = IORESOURCE_IO, \
|
||||
}
|
||||
|
||||
static struct resource regulator_resources[] = {
|
||||
MAX8925_REG_RESOURCE(SDCTL1, SDCTL1),
|
||||
MAX8925_REG_RESOURCE(SDCTL2, SDCTL2),
|
||||
MAX8925_REG_RESOURCE(SDCTL3, SDCTL3),
|
||||
MAX8925_REG_RESOURCE(LDOCTL1, LDOCTL1),
|
||||
MAX8925_REG_RESOURCE(LDOCTL2, LDOCTL2),
|
||||
MAX8925_REG_RESOURCE(LDOCTL3, LDOCTL3),
|
||||
MAX8925_REG_RESOURCE(LDOCTL4, LDOCTL4),
|
||||
MAX8925_REG_RESOURCE(LDOCTL5, LDOCTL5),
|
||||
MAX8925_REG_RESOURCE(LDOCTL6, LDOCTL6),
|
||||
MAX8925_REG_RESOURCE(LDOCTL7, LDOCTL7),
|
||||
MAX8925_REG_RESOURCE(LDOCTL8, LDOCTL8),
|
||||
MAX8925_REG_RESOURCE(LDOCTL9, LDOCTL9),
|
||||
MAX8925_REG_RESOURCE(LDOCTL10, LDOCTL10),
|
||||
MAX8925_REG_RESOURCE(LDOCTL11, LDOCTL11),
|
||||
MAX8925_REG_RESOURCE(LDOCTL12, LDOCTL12),
|
||||
MAX8925_REG_RESOURCE(LDOCTL13, LDOCTL13),
|
||||
MAX8925_REG_RESOURCE(LDOCTL14, LDOCTL14),
|
||||
MAX8925_REG_RESOURCE(LDOCTL15, LDOCTL15),
|
||||
MAX8925_REG_RESOURCE(LDOCTL16, LDOCTL16),
|
||||
MAX8925_REG_RESOURCE(LDOCTL17, LDOCTL17),
|
||||
MAX8925_REG_RESOURCE(LDOCTL18, LDOCTL18),
|
||||
MAX8925_REG_RESOURCE(LDOCTL19, LDOCTL19),
|
||||
MAX8925_REG_RESOURCE(LDOCTL20, LDOCTL20),
|
||||
};
|
||||
|
||||
#define MAX8925_REG_DEVS(_id) \
|
||||
{ \
|
||||
.name = "max8925-regulator", \
|
||||
.num_resources = 1, \
|
||||
.resources = ®ulator_resources[MAX8925_ID_##_id], \
|
||||
.id = MAX8925_ID_##_id, \
|
||||
}
|
||||
|
||||
static struct mfd_cell regulator_devs[] = {
|
||||
MAX8925_REG_DEVS(SD1),
|
||||
MAX8925_REG_DEVS(SD2),
|
||||
MAX8925_REG_DEVS(SD3),
|
||||
MAX8925_REG_DEVS(LDO1),
|
||||
MAX8925_REG_DEVS(LDO2),
|
||||
MAX8925_REG_DEVS(LDO3),
|
||||
MAX8925_REG_DEVS(LDO4),
|
||||
MAX8925_REG_DEVS(LDO5),
|
||||
MAX8925_REG_DEVS(LDO6),
|
||||
MAX8925_REG_DEVS(LDO7),
|
||||
MAX8925_REG_DEVS(LDO8),
|
||||
MAX8925_REG_DEVS(LDO9),
|
||||
MAX8925_REG_DEVS(LDO10),
|
||||
MAX8925_REG_DEVS(LDO11),
|
||||
MAX8925_REG_DEVS(LDO12),
|
||||
MAX8925_REG_DEVS(LDO13),
|
||||
MAX8925_REG_DEVS(LDO14),
|
||||
MAX8925_REG_DEVS(LDO15),
|
||||
MAX8925_REG_DEVS(LDO16),
|
||||
MAX8925_REG_DEVS(LDO17),
|
||||
MAX8925_REG_DEVS(LDO18),
|
||||
MAX8925_REG_DEVS(LDO19),
|
||||
MAX8925_REG_DEVS(LDO20),
|
||||
};
|
||||
|
||||
enum {
|
||||
FLAGS_ADC = 1, /* register in ADC component */
|
||||
FLAGS_RTC, /* register in RTC component */
|
||||
};
|
||||
|
||||
struct max8925_irq_data {
|
||||
int reg;
|
||||
int mask_reg;
|
||||
int enable; /* enable or not */
|
||||
int offs; /* bit offset in mask register */
|
||||
int flags;
|
||||
int tsc_irq;
|
||||
};
|
||||
|
||||
static struct max8925_irq_data max8925_irqs[] = {
|
||||
[MAX8925_IRQ_VCHG_DC_OVP] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_DC_F] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_DC_R] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_USB_OVP] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_USB_F] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_USB_R] = {
|
||||
.reg = MAX8925_CHG_IRQ1,
|
||||
.mask_reg = MAX8925_CHG_IRQ1_MASK,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_THM_OK_R] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_THM_OK_F] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_SYSLOW_F] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_SYSLOW_R] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_RST] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_DONE] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_TOPOFF] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 6,
|
||||
},
|
||||
[MAX8925_IRQ_VCHG_TMR_FAULT] = {
|
||||
.reg = MAX8925_CHG_IRQ2,
|
||||
.mask_reg = MAX8925_CHG_IRQ2_MASK,
|
||||
.offs = 1 << 7,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_RSTIN] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_MPL] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SW_3SEC] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 2,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_EXTON_F] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 3,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_EXTON_R] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 4,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SW_1SEC] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 5,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SW_F] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 6,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SW_R] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ1,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ1_MASK,
|
||||
.offs = 1 << 7,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SYSCKEN_F] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ2,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ2_MASK,
|
||||
.offs = 1 << 0,
|
||||
},
|
||||
[MAX8925_IRQ_GPM_SYSCKEN_R] = {
|
||||
.reg = MAX8925_ON_OFF_IRQ2,
|
||||
.mask_reg = MAX8925_ON_OFF_IRQ2_MASK,
|
||||
.offs = 1 << 1,
|
||||
},
|
||||
[MAX8925_IRQ_RTC_ALARM1] = {
|
||||
.reg = MAX8925_RTC_IRQ,
|
||||
.mask_reg = MAX8925_RTC_IRQ_MASK,
|
||||
.offs = 1 << 2,
|
||||
.flags = FLAGS_RTC,
|
||||
},
|
||||
[MAX8925_IRQ_RTC_ALARM0] = {
|
||||
.reg = MAX8925_RTC_IRQ,
|
||||
.mask_reg = MAX8925_RTC_IRQ_MASK,
|
||||
.offs = 1 << 3,
|
||||
.flags = FLAGS_RTC,
|
||||
},
|
||||
[MAX8925_IRQ_TSC_STICK] = {
|
||||
.reg = MAX8925_TSC_IRQ,
|
||||
.mask_reg = MAX8925_TSC_IRQ_MASK,
|
||||
.offs = 1 << 0,
|
||||
.flags = FLAGS_ADC,
|
||||
.tsc_irq = 1,
|
||||
},
|
||||
[MAX8925_IRQ_TSC_NSTICK] = {
|
||||
.reg = MAX8925_TSC_IRQ,
|
||||
.mask_reg = MAX8925_TSC_IRQ_MASK,
|
||||
.offs = 1 << 1,
|
||||
.flags = FLAGS_ADC,
|
||||
.tsc_irq = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static inline struct max8925_irq_data *irq_to_max8925(struct max8925_chip *chip,
|
||||
int irq)
|
||||
{
|
||||
return &max8925_irqs[irq - chip->irq_base];
|
||||
}
|
||||
|
||||
static irqreturn_t max8925_irq(int irq, void *data)
|
||||
{
|
||||
struct max8925_chip *chip = data;
|
||||
struct max8925_irq_data *irq_data;
|
||||
struct i2c_client *i2c;
|
||||
int read_reg = -1, value = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) {
|
||||
irq_data = &max8925_irqs[i];
|
||||
/* TSC IRQ should be serviced in max8925_tsc_irq() */
|
||||
if (irq_data->tsc_irq)
|
||||
continue;
|
||||
if (irq_data->flags == FLAGS_RTC)
|
||||
i2c = chip->rtc;
|
||||
else if (irq_data->flags == FLAGS_ADC)
|
||||
i2c = chip->adc;
|
||||
else
|
||||
i2c = chip->i2c;
|
||||
if (read_reg != irq_data->reg) {
|
||||
read_reg = irq_data->reg;
|
||||
value = max8925_reg_read(i2c, irq_data->reg);
|
||||
}
|
||||
if (value & irq_data->enable)
|
||||
handle_nested_irq(chip->irq_base + i);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t max8925_tsc_irq(int irq, void *data)
|
||||
{
|
||||
struct max8925_chip *chip = data;
|
||||
struct max8925_irq_data *irq_data;
|
||||
struct i2c_client *i2c;
|
||||
int read_reg = -1, value = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) {
|
||||
irq_data = &max8925_irqs[i];
|
||||
/* non TSC IRQ should be serviced in max8925_irq() */
|
||||
if (!irq_data->tsc_irq)
|
||||
continue;
|
||||
if (irq_data->flags == FLAGS_RTC)
|
||||
i2c = chip->rtc;
|
||||
else if (irq_data->flags == FLAGS_ADC)
|
||||
i2c = chip->adc;
|
||||
else
|
||||
i2c = chip->i2c;
|
||||
if (read_reg != irq_data->reg) {
|
||||
read_reg = irq_data->reg;
|
||||
value = max8925_reg_read(i2c, irq_data->reg);
|
||||
}
|
||||
if (value & irq_data->enable)
|
||||
handle_nested_irq(chip->irq_base + i);
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void max8925_irq_lock(unsigned int irq)
|
||||
{
|
||||
struct max8925_chip *chip = get_irq_chip_data(irq);
|
||||
|
||||
mutex_lock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static void max8925_irq_sync_unlock(unsigned int irq)
|
||||
{
|
||||
struct max8925_chip *chip = get_irq_chip_data(irq);
|
||||
struct max8925_irq_data *irq_data;
|
||||
static unsigned char cache_chg[2] = {0xff, 0xff};
|
||||
static unsigned char cache_on[2] = {0xff, 0xff};
|
||||
static unsigned char cache_rtc = 0xff, cache_tsc = 0xff;
|
||||
unsigned char irq_chg[2], irq_on[2];
|
||||
unsigned char irq_rtc, irq_tsc;
|
||||
int i;
|
||||
|
||||
/* Load cached value. In initial, all IRQs are masked */
|
||||
irq_chg[0] = cache_chg[0];
|
||||
irq_chg[1] = cache_chg[1];
|
||||
irq_on[0] = cache_on[0];
|
||||
irq_on[1] = cache_on[1];
|
||||
irq_rtc = cache_rtc;
|
||||
irq_tsc = cache_tsc;
|
||||
for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) {
|
||||
irq_data = &max8925_irqs[i];
|
||||
switch (irq_data->mask_reg) {
|
||||
case MAX8925_CHG_IRQ1_MASK:
|
||||
irq_chg[0] &= irq_data->enable;
|
||||
break;
|
||||
case MAX8925_CHG_IRQ2_MASK:
|
||||
irq_chg[1] &= irq_data->enable;
|
||||
break;
|
||||
case MAX8925_ON_OFF_IRQ1_MASK:
|
||||
irq_on[0] &= irq_data->enable;
|
||||
break;
|
||||
case MAX8925_ON_OFF_IRQ2_MASK:
|
||||
irq_on[1] &= irq_data->enable;
|
||||
break;
|
||||
case MAX8925_RTC_IRQ_MASK:
|
||||
irq_rtc &= irq_data->enable;
|
||||
break;
|
||||
case MAX8925_TSC_IRQ_MASK:
|
||||
irq_tsc &= irq_data->enable;
|
||||
break;
|
||||
default:
|
||||
dev_err(chip->dev, "wrong IRQ\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* update mask into registers */
|
||||
if (cache_chg[0] != irq_chg[0]) {
|
||||
cache_chg[0] = irq_chg[0];
|
||||
max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK,
|
||||
irq_chg[0]);
|
||||
}
|
||||
if (cache_chg[1] != irq_chg[1]) {
|
||||
cache_chg[1] = irq_chg[1];
|
||||
max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK,
|
||||
irq_chg[1]);
|
||||
}
|
||||
if (cache_on[0] != irq_on[0]) {
|
||||
cache_on[0] = irq_on[0];
|
||||
max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK,
|
||||
irq_on[0]);
|
||||
}
|
||||
if (cache_on[1] != irq_on[1]) {
|
||||
cache_on[1] = irq_on[1];
|
||||
max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK,
|
||||
irq_on[1]);
|
||||
}
|
||||
if (cache_rtc != irq_rtc) {
|
||||
cache_rtc = irq_rtc;
|
||||
max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, irq_rtc);
|
||||
}
|
||||
if (cache_tsc != irq_tsc) {
|
||||
cache_tsc = irq_tsc;
|
||||
max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, irq_tsc);
|
||||
}
|
||||
|
||||
mutex_unlock(&chip->irq_lock);
|
||||
}
|
||||
|
||||
static void max8925_irq_enable(unsigned int irq)
|
||||
{
|
||||
struct max8925_chip *chip = get_irq_chip_data(irq);
|
||||
max8925_irqs[irq - chip->irq_base].enable
|
||||
= max8925_irqs[irq - chip->irq_base].offs;
|
||||
}
|
||||
|
||||
static void max8925_irq_disable(unsigned int irq)
|
||||
{
|
||||
struct max8925_chip *chip = get_irq_chip_data(irq);
|
||||
max8925_irqs[irq - chip->irq_base].enable = 0;
|
||||
}
|
||||
|
||||
static struct irq_chip max8925_irq_chip = {
|
||||
.name = "max8925",
|
||||
.bus_lock = max8925_irq_lock,
|
||||
.bus_sync_unlock = max8925_irq_sync_unlock,
|
||||
.enable = max8925_irq_enable,
|
||||
.disable = max8925_irq_disable,
|
||||
};
|
||||
|
||||
static int max8925_irq_init(struct max8925_chip *chip, int irq,
|
||||
struct max8925_platform_data *pdata)
|
||||
{
|
||||
unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
|
||||
struct irq_desc *desc;
|
||||
int i, ret;
|
||||
int __irq;
|
||||
|
||||
if (!pdata || !pdata->irq_base) {
|
||||
dev_warn(chip->dev, "No interrupt support on IRQ base\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* clear all interrupts */
|
||||
max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ1);
|
||||
max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ2);
|
||||
max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ1);
|
||||
max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ2);
|
||||
max8925_reg_read(chip->rtc, MAX8925_RTC_IRQ);
|
||||
max8925_reg_read(chip->adc, MAX8925_TSC_IRQ);
|
||||
/* mask all interrupts */
|
||||
max8925_reg_write(chip->rtc, MAX8925_ALARM0_CNTL, 0);
|
||||
max8925_reg_write(chip->rtc, MAX8925_ALARM1_CNTL, 0);
|
||||
max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, 0xff);
|
||||
max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK, 0xff);
|
||||
max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff);
|
||||
max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, 0xff);
|
||||
max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, 0xff);
|
||||
max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0xff);
|
||||
|
||||
mutex_init(&chip->irq_lock);
|
||||
chip->core_irq = irq;
|
||||
chip->irq_base = pdata->irq_base;
|
||||
desc = irq_to_desc(chip->core_irq);
|
||||
|
||||
/* register with genirq */
|
||||
for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) {
|
||||
__irq = i + chip->irq_base;
|
||||
set_irq_chip_data(__irq, chip);
|
||||
set_irq_chip_and_handler(__irq, &max8925_irq_chip,
|
||||
handle_edge_irq);
|
||||
set_irq_nested_thread(__irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(__irq, IRQF_VALID);
|
||||
#else
|
||||
set_irq_noprobe(__irq);
|
||||
#endif
|
||||
}
|
||||
if (!irq) {
|
||||
dev_warn(chip->dev, "No interrupt support on core IRQ\n");
|
||||
goto tsc_irq;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(irq, NULL, max8925_irq, flags,
|
||||
"max8925", chip);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret);
|
||||
chip->core_irq = 0;
|
||||
}
|
||||
tsc_irq:
|
||||
if (!pdata->tsc_irq) {
|
||||
dev_warn(chip->dev, "No interrupt support on TSC IRQ\n");
|
||||
return 0;
|
||||
}
|
||||
chip->tsc_irq = pdata->tsc_irq;
|
||||
|
||||
ret = request_threaded_irq(chip->tsc_irq, NULL, max8925_tsc_irq,
|
||||
flags, "max8925-tsc", chip);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Failed to request TSC IRQ: %d\n", ret);
|
||||
chip->tsc_irq = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __devinit max8925_device_init(struct max8925_chip *chip,
|
||||
struct max8925_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
max8925_irq_init(chip, chip->i2c->irq, pdata);
|
||||
|
||||
if (pdata && (pdata->power || pdata->touch)) {
|
||||
/* enable ADC to control internal reference */
|
||||
max8925_set_bits(chip->i2c, MAX8925_RESET_CNFG, 1, 1);
|
||||
/* enable internal reference for ADC */
|
||||
max8925_set_bits(chip->adc, MAX8925_TSC_CNFG1, 3, 2);
|
||||
/* check for internal reference IRQ */
|
||||
do {
|
||||
ret = max8925_reg_read(chip->adc, MAX8925_TSC_IRQ);
|
||||
} while (ret & MAX8925_NREF_OK);
|
||||
/* enaable ADC scheduler, interval is 1 second */
|
||||
max8925_set_bits(chip->adc, MAX8925_ADC_SCHED, 3, 2);
|
||||
}
|
||||
|
||||
/* enable Momentary Power Loss */
|
||||
max8925_set_bits(chip->rtc, MAX8925_MPL_CNTL, 1 << 4, 1 << 4);
|
||||
|
||||
ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0],
|
||||
ARRAY_SIZE(rtc_devs),
|
||||
&rtc_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add rtc subdev\n");
|
||||
goto out;
|
||||
}
|
||||
if (pdata && pdata->regulator[0]) {
|
||||
ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0],
|
||||
ARRAY_SIZE(regulator_devs),
|
||||
®ulator_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add regulator subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata && pdata->backlight) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0],
|
||||
ARRAY_SIZE(backlight_devs),
|
||||
&backlight_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add backlight subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata && pdata->power) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &power_devs[0],
|
||||
ARRAY_SIZE(power_devs),
|
||||
&power_supply_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add power supply "
|
||||
"subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata && pdata->touch) {
|
||||
ret = mfd_add_devices(chip->dev, 0, &touch_devs[0],
|
||||
ARRAY_SIZE(touch_devs),
|
||||
&touch_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add touch subdev\n");
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_dev:
|
||||
mfd_remove_devices(chip->dev);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void __devexit max8925_device_exit(struct max8925_chip *chip)
|
||||
{
|
||||
if (chip->core_irq)
|
||||
free_irq(chip->core_irq, chip);
|
||||
if (chip->tsc_irq)
|
||||
free_irq(chip->tsc_irq, chip);
|
||||
mfd_remove_devices(chip->dev);
|
||||
}
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* I2C driver for Maxim MAX8925
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
#define RTC_I2C_ADDR 0x68
|
||||
#define ADC_I2C_ADDR 0x47
|
||||
|
||||
static inline int max8925_read_device(struct i2c_client *i2c,
|
||||
int reg, int bytes, void *dest)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (bytes > 1)
|
||||
ret = i2c_smbus_read_i2c_block_data(i2c, reg, bytes, dest);
|
||||
else {
|
||||
ret = i2c_smbus_read_byte_data(i2c, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*(unsigned char *)dest = (unsigned char)ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int max8925_write_device(struct i2c_client *i2c,
|
||||
int reg, int bytes, void *src)
|
||||
{
|
||||
unsigned char buf[bytes + 1];
|
||||
int ret;
|
||||
|
||||
buf[0] = (unsigned char)reg;
|
||||
memcpy(&buf[1], src, bytes);
|
||||
|
||||
ret = i2c_master_send(i2c, buf, bytes + 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int max8925_reg_read(struct i2c_client *i2c, int reg)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(i2c);
|
||||
unsigned char data = 0;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = max8925_read_device(i2c, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return (int)data;
|
||||
}
|
||||
EXPORT_SYMBOL(max8925_reg_read);
|
||||
|
||||
int max8925_reg_write(struct i2c_client *i2c, int reg,
|
||||
unsigned char data)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = max8925_write_device(i2c, reg, 1, &data);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(max8925_reg_write);
|
||||
|
||||
int max8925_bulk_read(struct i2c_client *i2c, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = max8925_read_device(i2c, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(max8925_bulk_read);
|
||||
|
||||
int max8925_bulk_write(struct i2c_client *i2c, int reg,
|
||||
int count, unsigned char *buf)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(i2c);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = max8925_write_device(i2c, reg, count, buf);
|
||||
mutex_unlock(&chip->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(max8925_bulk_write);
|
||||
|
||||
int max8925_set_bits(struct i2c_client *i2c, int reg,
|
||||
unsigned char mask, unsigned char data)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(i2c);
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->io_lock);
|
||||
ret = max8925_read_device(i2c, reg, 1, &value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
value &= ~mask;
|
||||
value |= data;
|
||||
ret = max8925_write_device(i2c, reg, 1, &value);
|
||||
out:
|
||||
mutex_unlock(&chip->io_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(max8925_set_bits);
|
||||
|
||||
|
||||
static const struct i2c_device_id max8925_id_table[] = {
|
||||
{ "max8925", 0 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max8925_id_table);
|
||||
|
||||
static int __devinit max8925_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct max8925_platform_data *pdata = client->dev.platform_data;
|
||||
static struct max8925_chip *chip;
|
||||
|
||||
if (!pdata) {
|
||||
pr_info("%s: platform data is missing\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chip = kzalloc(sizeof(struct max8925_chip), GFP_KERNEL);
|
||||
if (chip == NULL)
|
||||
return -ENOMEM;
|
||||
chip->i2c = client;
|
||||
chip->dev = &client->dev;
|
||||
i2c_set_clientdata(client, chip);
|
||||
dev_set_drvdata(chip->dev, chip);
|
||||
mutex_init(&chip->io_lock);
|
||||
|
||||
chip->rtc = i2c_new_dummy(chip->i2c->adapter, RTC_I2C_ADDR);
|
||||
i2c_set_clientdata(chip->rtc, chip);
|
||||
|
||||
chip->adc = i2c_new_dummy(chip->i2c->adapter, ADC_I2C_ADDR);
|
||||
i2c_set_clientdata(chip->adc, chip);
|
||||
|
||||
max8925_device_init(chip, pdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit max8925_remove(struct i2c_client *client)
|
||||
{
|
||||
struct max8925_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
max8925_device_exit(chip);
|
||||
i2c_unregister_device(chip->adc);
|
||||
i2c_unregister_device(chip->rtc);
|
||||
i2c_set_clientdata(chip->adc, NULL);
|
||||
i2c_set_clientdata(chip->rtc, NULL);
|
||||
i2c_set_clientdata(chip->i2c, NULL);
|
||||
kfree(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver max8925_driver = {
|
||||
.driver = {
|
||||
.name = "max8925",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max8925_probe,
|
||||
.remove = __devexit_p(max8925_remove),
|
||||
.id_table = max8925_id_table,
|
||||
};
|
||||
|
||||
static int __init max8925_i2c_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&max8925_driver);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to register MAX8925 I2C driver: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
subsys_initcall(max8925_i2c_init);
|
||||
|
||||
static void __exit max8925_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max8925_driver);
|
||||
}
|
||||
module_exit(max8925_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("I2C Driver for Maxim 8925");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/mfd/core.h>
|
||||
|
||||
static int mfd_add_device(struct device *parent, int id,
|
||||
|
@ -62,6 +63,10 @@ static int mfd_add_device(struct device *parent, int id,
|
|||
res[r].start = cell->resources[r].start;
|
||||
res[r].end = cell->resources[r].end;
|
||||
}
|
||||
|
||||
ret = acpi_check_resource_conflict(res);
|
||||
if (ret)
|
||||
goto fail_res;
|
||||
}
|
||||
|
||||
platform_device_add_resources(pdev, res, cell->num_resources);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tmio.h>
|
||||
#include <linux/mfd/sh_mobile_sdhi.h>
|
||||
|
@ -95,9 +95,9 @@ static int __init sh_mobile_sdhi_probe(struct platform_device *pdev)
|
|||
|
||||
clk_enable(priv->clk);
|
||||
|
||||
/* FIXME: silly const unsigned int hclk */
|
||||
*(unsigned int *)&priv->mmc_data.hclk = clk_get_rate(priv->clk);
|
||||
priv->mmc_data.hclk = clk_get_rate(priv->clk);
|
||||
priv->mmc_data.set_pwr = sh_mobile_sdhi_set_pwr;
|
||||
priv->mmc_data.capabilities = MMC_CAP_MMC_HIGHSPEED;
|
||||
|
||||
memcpy(&priv->cell_mmc, &sh_mobile_sdhi_cell, sizeof(priv->cell_mmc));
|
||||
priv->cell_mmc.driver_data = &priv->mmc_data;
|
||||
|
|
|
@ -1430,7 +1430,7 @@ static int __devinit sm501_plat_probe(struct platform_device *dev)
|
|||
}
|
||||
|
||||
sm->regs_claim = request_mem_region(sm->io_res->start,
|
||||
0x100, "sm501");
|
||||
resource_size(sm->io_res), "sm501");
|
||||
|
||||
if (sm->regs_claim == NULL) {
|
||||
dev_err(&dev->dev, "cannot claim registers\n");
|
||||
|
@ -1440,8 +1440,7 @@ static int __devinit sm501_plat_probe(struct platform_device *dev)
|
|||
|
||||
platform_set_drvdata(dev, sm);
|
||||
|
||||
sm->regs = ioremap(sm->io_res->start,
|
||||
(sm->io_res->end - sm->io_res->start) - 1);
|
||||
sm->regs = ioremap(sm->io_res->start, resource_size(sm->io_res));
|
||||
|
||||
if (sm->regs == NULL) {
|
||||
dev_err(&dev->dev, "cannot remap registers\n");
|
||||
|
@ -1645,7 +1644,7 @@ static int __devinit sm501_pci_probe(struct pci_dev *dev,
|
|||
sm->mem_res = &dev->resource[0];
|
||||
|
||||
sm->regs_claim = request_mem_region(sm->io_res->start,
|
||||
0x100, "sm501");
|
||||
resource_size(sm->io_res), "sm501");
|
||||
if (sm->regs_claim == NULL) {
|
||||
dev_err(&dev->dev, "cannot claim registers\n");
|
||||
err= -EBUSY;
|
||||
|
|
|
@ -360,7 +360,7 @@ static int t7l66xb_probe(struct platform_device *dev)
|
|||
if (ret)
|
||||
goto err_request_scr;
|
||||
|
||||
t7l66xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1);
|
||||
t7l66xb->scr = ioremap(rscr->start, resource_size(rscr));
|
||||
if (!t7l66xb->scr) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ioremap;
|
||||
|
@ -403,12 +403,12 @@ static int t7l66xb_probe(struct platform_device *dev)
|
|||
err_ioremap:
|
||||
release_resource(&t7l66xb->rscr);
|
||||
err_request_scr:
|
||||
kfree(t7l66xb);
|
||||
clk_put(t7l66xb->clk48m);
|
||||
err_clk48m_get:
|
||||
clk_put(t7l66xb->clk32k);
|
||||
err_clk32k_get:
|
||||
err_noirq:
|
||||
kfree(t7l66xb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -647,7 +647,7 @@ static int __devinit tc6393xb_probe(struct platform_device *dev)
|
|||
if (ret)
|
||||
goto err_request_scr;
|
||||
|
||||
tc6393xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1);
|
||||
tc6393xb->scr = ioremap(rscr->start, resource_size(rscr));
|
||||
if (!tc6393xb->scr) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ioremap;
|
||||
|
|
|
@ -58,13 +58,6 @@
|
|||
|
||||
#define DRIVER_NAME "twl"
|
||||
|
||||
#if defined(CONFIG_TWL4030_BCI_BATTERY) || \
|
||||
defined(CONFIG_TWL4030_BCI_BATTERY_MODULE)
|
||||
#define twl_has_bci() true
|
||||
#else
|
||||
#define twl_has_bci() false
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_KEYBOARD_TWL4030) || defined(CONFIG_KEYBOARD_TWL4030_MODULE)
|
||||
#define twl_has_keypad() true
|
||||
#else
|
||||
|
@ -130,7 +123,7 @@
|
|||
#define TWL_NUM_SLAVES 4
|
||||
|
||||
#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \
|
||||
|| defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE)
|
||||
|| defined(CONFIG_INPUT_TWL4030_PWRBUTTON_MODULE)
|
||||
#define twl_has_pwrbutton() true
|
||||
#else
|
||||
#define twl_has_pwrbutton() false
|
||||
|
@ -205,6 +198,7 @@
|
|||
/* subchip/slave 3 0x4B - AUDIO */
|
||||
#define TWL6030_BASEADD_AUDIO 0x0000
|
||||
#define TWL6030_BASEADD_RSV 0x0000
|
||||
#define TWL6030_BASEADD_ZERO 0x0000
|
||||
|
||||
/* Few power values */
|
||||
#define R_CFG_BOOT 0x05
|
||||
|
@ -320,9 +314,11 @@ static struct twl_mapping twl6030_map[] = {
|
|||
{ SUB_CHIP_ID1, TWL6030_BASEADD_CHARGER },
|
||||
{ SUB_CHIP_ID1, TWL6030_BASEADD_GASGAUGE },
|
||||
{ SUB_CHIP_ID1, TWL6030_BASEADD_PWM },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_RSV },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_RSV },
|
||||
{ SUB_CHIP_ID0, TWL6030_BASEADD_ZERO },
|
||||
{ SUB_CHIP_ID1, TWL6030_BASEADD_ZERO },
|
||||
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_ZERO },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_ZERO },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_RSV },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_RSV },
|
||||
{ SUB_CHIP_ID2, TWL6030_BASEADD_RSV },
|
||||
|
@ -588,18 +584,6 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
|
|||
struct device *child;
|
||||
unsigned sub_chip_id;
|
||||
|
||||
if (twl_has_bci() && pdata->bci &&
|
||||
!(features & (TPS_SUBSET | TWL5031))) {
|
||||
child = add_child(3, "twl4030_bci",
|
||||
pdata->bci, sizeof(*pdata->bci),
|
||||
false,
|
||||
/* irq0 = CHG_PRES, irq1 = BCI */
|
||||
pdata->irq_base + BCI_PRES_INTR_OFFSET,
|
||||
pdata->irq_base + BCI_INTR_OFFSET);
|
||||
if (IS_ERR(child))
|
||||
return PTR_ERR(child);
|
||||
}
|
||||
|
||||
if (twl_has_gpio() && pdata->gpio) {
|
||||
child = add_child(SUB_CHIP_ID1, "twl4030_gpio",
|
||||
pdata->gpio, sizeof(*pdata->gpio),
|
||||
|
@ -977,6 +961,7 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
int status;
|
||||
unsigned i;
|
||||
struct twl4030_platform_data *pdata = client->dev.platform_data;
|
||||
u8 temp;
|
||||
|
||||
if (!pdata) {
|
||||
dev_dbg(&client->dev, "no platform data?\n");
|
||||
|
@ -1044,6 +1029,18 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
goto fail;
|
||||
}
|
||||
|
||||
/* Disable TWL4030/TWL5030 I2C Pull-up on I2C1 and I2C4(SR) interface.
|
||||
* Program I2C_SCL_CTRL_PU(bit 0)=0, I2C_SDA_CTRL_PU (bit 2)=0,
|
||||
* SR_I2C_SCL_CTRL_PU(bit 4)=0 and SR_I2C_SDA_CTRL_PU(bit 6)=0.
|
||||
*/
|
||||
|
||||
if (twl_class_is_4030()) {
|
||||
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &temp, REG_GPPUPDCTR1);
|
||||
temp &= ~(SR_I2C_SDA_CTRL_PU | SR_I2C_SCL_CTRL_PU | \
|
||||
I2C_SDA_CTRL_PU | I2C_SCL_CTRL_PU);
|
||||
twl_i2c_write_u8(TWL4030_MODULE_INTBR, temp, REG_GPPUPDCTR1);
|
||||
}
|
||||
|
||||
status = add_children(pdata, id->driver_data);
|
||||
fail:
|
||||
if (status < 0)
|
||||
|
|
|
@ -405,7 +405,7 @@ static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig)
|
|||
|
||||
if (rconfig->remap_sleep != TWL4030_RESCONFIG_UNDEF) {
|
||||
remap &= ~SLEEP_STATE_MASK;
|
||||
remap |= rconfig->remap_off << SLEEP_STATE_SHIFT;
|
||||
remap |= rconfig->remap_sleep << SLEEP_STATE_SHIFT;
|
||||
}
|
||||
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER,
|
||||
|
@ -461,6 +461,56 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
int twl4030_remove_script(u8 flags)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1,
|
||||
R_PROTECT_KEY);
|
||||
if (err) {
|
||||
pr_err("twl4030: unable to unlock PROTECT_KEY\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2,
|
||||
R_PROTECT_KEY);
|
||||
if (err) {
|
||||
pr_err("twl4030: unable to unlock PROTECT_KEY\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (flags & TWL4030_WRST_SCRIPT) {
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT,
|
||||
R_SEQ_ADD_WARM);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
if (flags & TWL4030_WAKEUP12_SCRIPT) {
|
||||
if (err)
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT,
|
||||
R_SEQ_ADD_S2A12);
|
||||
return err;
|
||||
}
|
||||
if (flags & TWL4030_WAKEUP3_SCRIPT) {
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT,
|
||||
R_SEQ_ADD_S2A3);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
if (flags & TWL4030_SLEEP_SCRIPT) {
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT,
|
||||
R_SEQ_ADD_A2S);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY);
|
||||
if (err)
|
||||
pr_err("TWL4030 Unable to relock registers\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts)
|
||||
{
|
||||
int err = 0;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <linux/mutex.h>
|
||||
#include <linux/mfd/ucb1x00.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/semaphore.h>
|
||||
|
||||
#include <mach/dma.h>
|
||||
#include <mach/hardware.h>
|
||||
|
|
|
@ -321,7 +321,6 @@ EXPORT_SYMBOL_GPL(wm831x_set_bits);
|
|||
*/
|
||||
int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
|
||||
{
|
||||
int tries = 10;
|
||||
int ret, src;
|
||||
|
||||
mutex_lock(&wm831x->auxadc_lock);
|
||||
|
@ -349,13 +348,14 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
|
|||
goto disable;
|
||||
}
|
||||
|
||||
do {
|
||||
msleep(1);
|
||||
/* Ignore the result to allow us to soldier on without IRQ hookup */
|
||||
wait_for_completion_timeout(&wm831x->auxadc_done, msecs_to_jiffies(5));
|
||||
|
||||
ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL);
|
||||
if (ret < 0)
|
||||
ret = WM831X_AUX_CVT_ENA;
|
||||
} while ((ret & WM831X_AUX_CVT_ENA) && --tries);
|
||||
ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL);
|
||||
if (ret < 0) {
|
||||
dev_err(wm831x->dev, "AUXADC status read failed: %d\n", ret);
|
||||
goto disable;
|
||||
}
|
||||
|
||||
if (ret & WM831X_AUX_CVT_ENA) {
|
||||
dev_err(wm831x->dev, "Timed out reading AUXADC\n");
|
||||
|
@ -390,6 +390,15 @@ out:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wm831x_auxadc_read);
|
||||
|
||||
static irqreturn_t wm831x_auxadc_irq(int irq, void *irq_data)
|
||||
{
|
||||
struct wm831x *wm831x = irq_data;
|
||||
|
||||
complete(&wm831x->auxadc_done);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC
|
||||
*
|
||||
|
@ -1411,6 +1420,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
|
|||
mutex_init(&wm831x->io_lock);
|
||||
mutex_init(&wm831x->key_lock);
|
||||
mutex_init(&wm831x->auxadc_lock);
|
||||
init_completion(&wm831x->auxadc_done);
|
||||
dev_set_drvdata(wm831x->dev, wm831x);
|
||||
|
||||
ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID);
|
||||
|
@ -1449,18 +1459,33 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
|
|||
case WM8310:
|
||||
parent = WM8310;
|
||||
wm831x->num_gpio = 16;
|
||||
if (rev > 0) {
|
||||
wm831x->has_gpio_ena = 1;
|
||||
wm831x->has_cs_sts = 1;
|
||||
}
|
||||
|
||||
dev_info(wm831x->dev, "WM8310 revision %c\n", 'A' + rev);
|
||||
break;
|
||||
|
||||
case WM8311:
|
||||
parent = WM8311;
|
||||
wm831x->num_gpio = 16;
|
||||
if (rev > 0) {
|
||||
wm831x->has_gpio_ena = 1;
|
||||
wm831x->has_cs_sts = 1;
|
||||
}
|
||||
|
||||
dev_info(wm831x->dev, "WM8311 revision %c\n", 'A' + rev);
|
||||
break;
|
||||
|
||||
case WM8312:
|
||||
parent = WM8312;
|
||||
wm831x->num_gpio = 16;
|
||||
if (rev > 0) {
|
||||
wm831x->has_gpio_ena = 1;
|
||||
wm831x->has_cs_sts = 1;
|
||||
}
|
||||
|
||||
dev_info(wm831x->dev, "WM8312 revision %c\n", 'A' + rev);
|
||||
break;
|
||||
|
||||
|
@ -1508,6 +1533,16 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
|
|||
if (ret != 0)
|
||||
goto err;
|
||||
|
||||
if (wm831x->irq_base) {
|
||||
ret = request_threaded_irq(wm831x->irq_base +
|
||||
WM831X_IRQ_AUXADC_DATA,
|
||||
NULL, wm831x_auxadc_irq, 0,
|
||||
"auxadc", wm831x);
|
||||
if (ret < 0)
|
||||
dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
/* The core device is up, instantiate the subdevices. */
|
||||
switch (parent) {
|
||||
case WM8310:
|
||||
|
@ -1578,6 +1613,8 @@ static void wm831x_device_exit(struct wm831x *wm831x)
|
|||
{
|
||||
wm831x_otp_exit(wm831x);
|
||||
mfd_remove_devices(wm831x->dev);
|
||||
if (wm831x->irq_base)
|
||||
free_irq(wm831x->irq_base + WM831X_IRQ_AUXADC_DATA, wm831x);
|
||||
wm831x_irq_exit(wm831x);
|
||||
kfree(wm831x);
|
||||
}
|
||||
|
|
|
@ -339,7 +339,6 @@ EXPORT_SYMBOL_GPL(wm8350_reg_unlock);
|
|||
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;
|
||||
|
@ -363,12 +362,13 @@ int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref)
|
|||
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 ((reg & WM8350_AUXADC_POLL) && --tries);
|
||||
/* We ignore the result of the completion and just check for a
|
||||
* conversion result, allowing us to soldier on if the IRQ
|
||||
* infrastructure is not set up for the chip. */
|
||||
wait_for_completion_timeout(&wm8350->auxadc_done, msecs_to_jiffies(5));
|
||||
|
||||
if (!tries)
|
||||
reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1);
|
||||
if (reg & WM8350_AUXADC_POLL)
|
||||
dev_err(wm8350->dev, "adc chn %d read timeout\n", channel);
|
||||
else
|
||||
result = wm8350_reg_read(wm8350,
|
||||
|
@ -385,6 +385,15 @@ int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_read_auxadc);
|
||||
|
||||
static irqreturn_t wm8350_auxadc_irq(int irq, void *irq_data)
|
||||
{
|
||||
struct wm8350 *wm8350 = irq_data;
|
||||
|
||||
complete(&wm8350->auxadc_done);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cache is always host endian.
|
||||
*/
|
||||
|
@ -682,11 +691,22 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
|
|||
}
|
||||
|
||||
mutex_init(&wm8350->auxadc_mutex);
|
||||
init_completion(&wm8350->auxadc_done);
|
||||
|
||||
ret = wm8350_irq_init(wm8350, irq, pdata);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (wm8350->irq_base) {
|
||||
ret = request_threaded_irq(wm8350->irq_base +
|
||||
WM8350_IRQ_AUXADC_DATARDY,
|
||||
NULL, wm8350_auxadc_irq, 0,
|
||||
"auxadc", wm8350);
|
||||
if (ret < 0)
|
||||
dev_warn(wm8350->dev,
|
||||
"Failed to request AUXADC IRQ: %d\n", ret);
|
||||
}
|
||||
|
||||
if (pdata && pdata->init) {
|
||||
ret = pdata->init(wm8350);
|
||||
if (ret != 0) {
|
||||
|
@ -736,6 +756,9 @@ void wm8350_device_exit(struct wm8350 *wm8350)
|
|||
platform_device_unregister(wm8350->gpio.pdev);
|
||||
platform_device_unregister(wm8350->codec.pdev);
|
||||
|
||||
if (wm8350->irq_base)
|
||||
free_irq(wm8350->irq_base + WM8350_IRQ_AUXADC_DATARDY, wm8350);
|
||||
|
||||
wm8350_irq_exit(wm8350);
|
||||
|
||||
kfree(wm8350->reg_cache);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <linux/bug.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include <linux/mfd/wm8350/core.h>
|
||||
#include <linux/mfd/wm8350/audio.h>
|
||||
|
@ -29,8 +29,6 @@
|
|||
#include <linux/mfd/wm8350/supply.h>
|
||||
#include <linux/mfd/wm8350/wdt.h>
|
||||
|
||||
#define WM8350_NUM_IRQ_REGS 7
|
||||
|
||||
#define WM8350_INT_OFFSET_1 0
|
||||
#define WM8350_INT_OFFSET_2 1
|
||||
#define WM8350_POWER_UP_INT_OFFSET 2
|
||||
|
@ -366,19 +364,10 @@ static struct wm8350_irq_data wm8350_irqs[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq)
|
||||
static inline struct wm8350_irq_data *irq_to_wm8350_irq(struct wm8350 *wm8350,
|
||||
int irq)
|
||||
{
|
||||
mutex_lock(&wm8350->irq_mutex);
|
||||
|
||||
if (wm8350->irq[irq].handler)
|
||||
wm8350->irq[irq].handler(irq, wm8350->irq[irq].data);
|
||||
else {
|
||||
dev_err(wm8350->dev, "irq %d nobody cared. now masked.\n",
|
||||
irq);
|
||||
wm8350_mask_irq(wm8350, irq);
|
||||
}
|
||||
|
||||
mutex_unlock(&wm8350->irq_mutex);
|
||||
return &wm8350_irqs[irq - wm8350->irq_base];
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -386,7 +375,9 @@ static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq)
|
|||
* interrupts are clear on read the IRQ line will be reasserted and
|
||||
* the physical IRQ will be handled again if another interrupt is
|
||||
* asserted while we run - in the normal course of events this is a
|
||||
* rare occurrence so we save I2C/SPI reads.
|
||||
* rare occurrence so we save I2C/SPI reads. We're also assuming that
|
||||
* it's rare to get lots of interrupts firing simultaneously so try to
|
||||
* minimise I/O.
|
||||
*/
|
||||
static irqreturn_t wm8350_irq(int irq, void *irq_data)
|
||||
{
|
||||
|
@ -397,7 +388,6 @@ static irqreturn_t wm8350_irq(int irq, void *irq_data)
|
|||
struct wm8350_irq_data *data;
|
||||
int i;
|
||||
|
||||
/* TODO: Use block reads to improve performance? */
|
||||
level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS)
|
||||
& ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK);
|
||||
|
||||
|
@ -416,93 +406,101 @@ static irqreturn_t wm8350_irq(int irq, void *irq_data)
|
|||
sub_reg[data->reg] =
|
||||
wm8350_reg_read(wm8350, WM8350_INT_STATUS_1 +
|
||||
data->reg);
|
||||
sub_reg[data->reg] &=
|
||||
~wm8350_reg_read(wm8350,
|
||||
WM8350_INT_STATUS_1_MASK +
|
||||
data->reg);
|
||||
sub_reg[data->reg] &= ~wm8350->irq_masks[data->reg];
|
||||
read_done[data->reg] = 1;
|
||||
}
|
||||
|
||||
if (sub_reg[data->reg] & data->mask)
|
||||
wm8350_irq_call_handler(wm8350, i);
|
||||
handle_nested_irq(wm8350->irq_base + i);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int wm8350_register_irq(struct wm8350 *wm8350, int irq,
|
||||
irq_handler_t handler, unsigned long flags,
|
||||
const char *name, void *data)
|
||||
static void wm8350_irq_lock(unsigned int irq)
|
||||
{
|
||||
if (irq < 0 || irq >= WM8350_NUM_IRQ || !handler)
|
||||
return -EINVAL;
|
||||
struct wm8350 *wm8350 = get_irq_chip_data(irq);
|
||||
|
||||
if (wm8350->irq[irq].handler)
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&wm8350->irq_mutex);
|
||||
wm8350->irq[irq].handler = handler;
|
||||
wm8350->irq[irq].data = data;
|
||||
mutex_unlock(&wm8350->irq_mutex);
|
||||
|
||||
wm8350_unmask_irq(wm8350, irq);
|
||||
|
||||
return 0;
|
||||
mutex_lock(&wm8350->irq_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_register_irq);
|
||||
|
||||
int wm8350_free_irq(struct wm8350 *wm8350, int irq)
|
||||
static void wm8350_irq_sync_unlock(unsigned int irq)
|
||||
{
|
||||
if (irq < 0 || irq >= WM8350_NUM_IRQ)
|
||||
return -EINVAL;
|
||||
struct wm8350 *wm8350 = get_irq_chip_data(irq);
|
||||
int i;
|
||||
|
||||
wm8350_mask_irq(wm8350, irq);
|
||||
for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) {
|
||||
/* If there's been a change in the mask write it back
|
||||
* to the hardware. */
|
||||
if (wm8350->irq_masks[i] !=
|
||||
wm8350->reg_cache[WM8350_INT_STATUS_1_MASK + i])
|
||||
WARN_ON(wm8350_reg_write(wm8350,
|
||||
WM8350_INT_STATUS_1_MASK + i,
|
||||
wm8350->irq_masks[i]));
|
||||
}
|
||||
|
||||
mutex_lock(&wm8350->irq_mutex);
|
||||
wm8350->irq[irq].handler = NULL;
|
||||
mutex_unlock(&wm8350->irq_mutex);
|
||||
return 0;
|
||||
mutex_unlock(&wm8350->irq_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_free_irq);
|
||||
|
||||
int wm8350_mask_irq(struct wm8350 *wm8350, int irq)
|
||||
static void wm8350_irq_enable(unsigned int irq)
|
||||
{
|
||||
return wm8350_set_bits(wm8350, WM8350_INT_STATUS_1_MASK +
|
||||
wm8350_irqs[irq].reg,
|
||||
wm8350_irqs[irq].mask);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_mask_irq);
|
||||
struct wm8350 *wm8350 = get_irq_chip_data(irq);
|
||||
struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, irq);
|
||||
|
||||
int wm8350_unmask_irq(struct wm8350 *wm8350, int irq)
|
||||
{
|
||||
return wm8350_clear_bits(wm8350, WM8350_INT_STATUS_1_MASK +
|
||||
wm8350_irqs[irq].reg,
|
||||
wm8350_irqs[irq].mask);
|
||||
wm8350->irq_masks[irq_data->reg] &= ~irq_data->mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_unmask_irq);
|
||||
|
||||
static void wm8350_irq_disable(unsigned int irq)
|
||||
{
|
||||
struct wm8350 *wm8350 = get_irq_chip_data(irq);
|
||||
struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, irq);
|
||||
|
||||
wm8350->irq_masks[irq_data->reg] |= irq_data->mask;
|
||||
}
|
||||
|
||||
static struct irq_chip wm8350_irq_chip = {
|
||||
.name = "wm8350",
|
||||
.bus_lock = wm8350_irq_lock,
|
||||
.bus_sync_unlock = wm8350_irq_sync_unlock,
|
||||
.disable = wm8350_irq_disable,
|
||||
.enable = wm8350_irq_enable,
|
||||
};
|
||||
|
||||
int wm8350_irq_init(struct wm8350 *wm8350, int irq,
|
||||
struct wm8350_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
int ret, cur_irq, i;
|
||||
int flags = IRQF_ONESHOT;
|
||||
|
||||
if (!irq) {
|
||||
dev_err(wm8350->dev, "No IRQ configured\n");
|
||||
return -EINVAL;
|
||||
dev_warn(wm8350->dev, "No interrupt support, no core IRQ\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!pdata || !pdata->irq_base) {
|
||||
dev_warn(wm8350->dev, "No interrupt support, no IRQ base\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mask top level interrupts */
|
||||
wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0xFFFF);
|
||||
wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK, 0xFFFF);
|
||||
wm8350_reg_write(wm8350, WM8350_INT_STATUS_2_MASK, 0xFFFF);
|
||||
wm8350_reg_write(wm8350, WM8350_UNDER_VOLTAGE_INT_STATUS_MASK, 0xFFFF);
|
||||
wm8350_reg_write(wm8350, WM8350_GPIO_INT_STATUS_MASK, 0xFFFF);
|
||||
wm8350_reg_write(wm8350, WM8350_COMPARATOR_INT_STATUS_MASK, 0xFFFF);
|
||||
|
||||
mutex_init(&wm8350->irq_mutex);
|
||||
/* Mask all individual interrupts by default and cache the
|
||||
* masks. We read the masks back since there are unwritable
|
||||
* bits in the mask registers. */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) {
|
||||
wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK + i,
|
||||
0xFFFF);
|
||||
wm8350->irq_masks[i] =
|
||||
wm8350_reg_read(wm8350,
|
||||
WM8350_INT_STATUS_1_MASK + i);
|
||||
}
|
||||
|
||||
mutex_init(&wm8350->irq_lock);
|
||||
wm8350->chip_irq = irq;
|
||||
wm8350->irq_base = pdata->irq_base;
|
||||
|
||||
if (pdata && pdata->irq_high) {
|
||||
if (pdata->irq_high) {
|
||||
flags |= IRQF_TRIGGER_HIGH;
|
||||
|
||||
wm8350_set_bits(wm8350, WM8350_SYSTEM_CONTROL_1,
|
||||
|
@ -514,11 +512,32 @@ int wm8350_irq_init(struct wm8350 *wm8350, int irq,
|
|||
WM8350_IRQ_POL);
|
||||
}
|
||||
|
||||
/* Register with genirq */
|
||||
for (cur_irq = wm8350->irq_base;
|
||||
cur_irq < ARRAY_SIZE(wm8350_irqs) + wm8350->irq_base;
|
||||
cur_irq++) {
|
||||
set_irq_chip_data(cur_irq, wm8350);
|
||||
set_irq_chip_and_handler(cur_irq, &wm8350_irq_chip,
|
||||
handle_edge_irq);
|
||||
set_irq_nested_thread(cur_irq, 1);
|
||||
|
||||
/* ARM needs us to explicitly flag the IRQ as valid
|
||||
* and will set them noprobe when we do so. */
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(cur_irq, IRQF_VALID);
|
||||
#else
|
||||
set_irq_noprobe(cur_irq);
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(irq, NULL, wm8350_irq, flags,
|
||||
"wm8350", wm8350);
|
||||
if (ret != 0)
|
||||
dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret);
|
||||
|
||||
/* Allow interrupts to fire */
|
||||
wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
* wm8994-core.c -- Device access for Wolfson WM8994
|
||||
*
|
||||
* 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
#include <linux/mfd/wm8994/core.h>
|
||||
#include <linux/mfd/wm8994/pdata.h>
|
||||
#include <linux/mfd/wm8994/registers.h>
|
||||
|
||||
static int wm8994_read(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *dest)
|
||||
{
|
||||
int ret, i;
|
||||
u16 *buf = dest;
|
||||
|
||||
BUG_ON(bytes % 2);
|
||||
BUG_ON(bytes <= 0);
|
||||
|
||||
ret = wm8994->read_dev(wm8994, reg, bytes, dest);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < bytes / 2; i++) {
|
||||
buf[i] = be16_to_cpu(buf[i]);
|
||||
|
||||
dev_vdbg(wm8994->dev, "Read %04x from R%d(0x%x)\n",
|
||||
buf[i], reg + i, reg + i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* wm8994_reg_read: Read a single WM8994 register.
|
||||
*
|
||||
* @wm8994: Device to read from.
|
||||
* @reg: Register to read.
|
||||
*/
|
||||
int wm8994_reg_read(struct wm8994 *wm8994, unsigned short reg)
|
||||
{
|
||||
unsigned short val;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wm8994->io_lock);
|
||||
|
||||
ret = wm8994_read(wm8994, reg, 2, &val);
|
||||
|
||||
mutex_unlock(&wm8994->io_lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8994_reg_read);
|
||||
|
||||
/**
|
||||
* wm8994_bulk_read: Read multiple WM8994 registers
|
||||
*
|
||||
* @wm8994: Device to read from
|
||||
* @reg: First register
|
||||
* @count: Number of registers
|
||||
* @buf: Buffer to fill.
|
||||
*/
|
||||
int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg,
|
||||
int count, u16 *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wm8994->io_lock);
|
||||
|
||||
ret = wm8994_read(wm8994, reg, count * 2, buf);
|
||||
|
||||
mutex_unlock(&wm8994->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8994_bulk_read);
|
||||
|
||||
static int wm8994_write(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *src)
|
||||
{
|
||||
u16 *buf = src;
|
||||
int i;
|
||||
|
||||
BUG_ON(bytes % 2);
|
||||
BUG_ON(bytes <= 0);
|
||||
|
||||
for (i = 0; i < bytes / 2; i++) {
|
||||
dev_vdbg(wm8994->dev, "Write %04x to R%d(0x%x)\n",
|
||||
buf[i], reg + i, reg + i);
|
||||
|
||||
buf[i] = cpu_to_be16(buf[i]);
|
||||
}
|
||||
|
||||
return wm8994->write_dev(wm8994, reg, bytes, src);
|
||||
}
|
||||
|
||||
/**
|
||||
* wm8994_reg_write: Write a single WM8994 register.
|
||||
*
|
||||
* @wm8994: Device to write to.
|
||||
* @reg: Register to write to.
|
||||
* @val: Value to write.
|
||||
*/
|
||||
int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wm8994->io_lock);
|
||||
|
||||
ret = wm8994_write(wm8994, reg, 2, &val);
|
||||
|
||||
mutex_unlock(&wm8994->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8994_reg_write);
|
||||
|
||||
/**
|
||||
* wm8994_set_bits: Set the value of a bitfield in a WM8994 register
|
||||
*
|
||||
* @wm8994: Device to write to.
|
||||
* @reg: Register to write to.
|
||||
* @mask: Mask of bits to set.
|
||||
* @val: Value to set (unshifted)
|
||||
*/
|
||||
int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg,
|
||||
unsigned short mask, unsigned short val)
|
||||
{
|
||||
int ret;
|
||||
u16 r;
|
||||
|
||||
mutex_lock(&wm8994->io_lock);
|
||||
|
||||
ret = wm8994_read(wm8994, reg, 2, &r);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
r &= ~mask;
|
||||
r |= val;
|
||||
|
||||
ret = wm8994_write(wm8994, reg, 2, &r);
|
||||
|
||||
out:
|
||||
mutex_unlock(&wm8994->io_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8994_set_bits);
|
||||
|
||||
static struct mfd_cell wm8994_regulator_devs[] = {
|
||||
{ .name = "wm8994-ldo", .id = 1 },
|
||||
{ .name = "wm8994-ldo", .id = 2 },
|
||||
};
|
||||
|
||||
static struct mfd_cell wm8994_devs[] = {
|
||||
{ .name = "wm8994-codec" },
|
||||
{ .name = "wm8994-gpio" },
|
||||
};
|
||||
|
||||
/*
|
||||
* Supplies for the main bulk of CODEC; the LDO supplies are ignored
|
||||
* and should be handled via the standard regulator API supply
|
||||
* management.
|
||||
*/
|
||||
static const char *wm8994_main_supplies[] = {
|
||||
"DBVDD",
|
||||
"DCVDD",
|
||||
"AVDD1",
|
||||
"AVDD2",
|
||||
"CPVDD",
|
||||
"SPKVDD1",
|
||||
"SPKVDD2",
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8994_device_suspend(struct device *dev)
|
||||
{
|
||||
struct wm8994 *wm8994 = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
/* GPIO configuration state is saved here since we may be configuring
|
||||
* the GPIO alternate functions even if we're not using the gpiolib
|
||||
* driver for them.
|
||||
*/
|
||||
ret = wm8994_read(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2,
|
||||
&wm8994->gpio_regs);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to save GPIO registers: %d\n", ret);
|
||||
|
||||
/* For similar reasons we also stash the regulator states */
|
||||
ret = wm8994_read(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,
|
||||
&wm8994->ldo_regs);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to save LDO registers: %d\n", ret);
|
||||
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to disable supplies: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8994_device_resume(struct device *dev)
|
||||
{
|
||||
struct wm8994 *wm8994 = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to enable supplies: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,
|
||||
&wm8994->ldo_regs);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to restore LDO registers: %d\n", ret);
|
||||
|
||||
ret = wm8994_write(wm8994, WM8994_GPIO_1, WM8994_NUM_GPIO_REGS * 2,
|
||||
&wm8994->gpio_regs);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to restore GPIO registers: %d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_REGULATOR
|
||||
static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo)
|
||||
{
|
||||
struct wm8994_ldo_pdata *ldo_pdata;
|
||||
|
||||
if (!pdata)
|
||||
return 0;
|
||||
|
||||
ldo_pdata = &pdata->ldo[ldo];
|
||||
|
||||
if (!ldo_pdata->init_data)
|
||||
return 0;
|
||||
|
||||
return ldo_pdata->init_data->num_consumer_supplies != 0;
|
||||
}
|
||||
#else
|
||||
static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Instantiate the generic non-control parts of the device.
|
||||
*/
|
||||
static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq)
|
||||
{
|
||||
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
|
||||
int ret, i;
|
||||
|
||||
mutex_init(&wm8994->io_lock);
|
||||
dev_set_drvdata(wm8994->dev, wm8994);
|
||||
|
||||
/* Add the on-chip regulators first for bootstrapping */
|
||||
ret = mfd_add_devices(wm8994->dev, -1,
|
||||
wm8994_regulator_devs,
|
||||
ARRAY_SIZE(wm8994_regulator_devs),
|
||||
NULL, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(wm8994->dev, "Failed to add children: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) *
|
||||
ARRAY_SIZE(wm8994_main_supplies),
|
||||
GFP_KERNEL);
|
||||
if (!wm8994->supplies)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++)
|
||||
wm8994->supplies[i].supply = wm8994_main_supplies[i];
|
||||
|
||||
ret = regulator_bulk_get(wm8994->dev, ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(wm8994->dev, "Failed to get supplies: %d\n", ret);
|
||||
goto err_supplies;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(wm8994->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_get;
|
||||
}
|
||||
|
||||
ret = wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET);
|
||||
if (ret < 0) {
|
||||
dev_err(wm8994->dev, "Failed to read ID register\n");
|
||||
goto err_enable;
|
||||
}
|
||||
if (ret != 0x8994) {
|
||||
dev_err(wm8994->dev, "Device is not a WM8994, ID is %x\n",
|
||||
ret);
|
||||
ret = -EINVAL;
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
ret = wm8994_reg_read(wm8994, WM8994_CHIP_REVISION);
|
||||
if (ret < 0) {
|
||||
dev_err(wm8994->dev, "Failed to read revision register: %d\n",
|
||||
ret);
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
case 1:
|
||||
dev_warn(wm8994->dev, "revision %c not fully supported\n",
|
||||
'A' + ret);
|
||||
break;
|
||||
default:
|
||||
dev_info(wm8994->dev, "revision %c\n", 'A' + ret);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (pdata) {
|
||||
wm8994->gpio_base = pdata->gpio_base;
|
||||
|
||||
/* GPIO configuration is only applied if it's non-zero */
|
||||
for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) {
|
||||
if (pdata->gpio_defaults[i]) {
|
||||
wm8994_set_bits(wm8994, WM8994_GPIO_1 + i,
|
||||
0xffff,
|
||||
pdata->gpio_defaults[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* In some system designs where the regulators are not in use,
|
||||
* we can achieve a small reduction in leakage currents by
|
||||
* floating LDO outputs. This bit makes no difference if the
|
||||
* LDOs are enabled, it only affects cases where the LDOs were
|
||||
* in operation and are then disabled.
|
||||
*/
|
||||
for (i = 0; i < WM8994_NUM_LDO_REGS; i++) {
|
||||
if (wm8994_ldo_in_use(pdata, i))
|
||||
wm8994_set_bits(wm8994, WM8994_LDO_1 + i,
|
||||
WM8994_LDO1_DISCH, WM8994_LDO1_DISCH);
|
||||
else
|
||||
wm8994_set_bits(wm8994, WM8994_LDO_1 + i,
|
||||
WM8994_LDO1_DISCH, 0);
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(wm8994->dev, -1,
|
||||
wm8994_devs, ARRAY_SIZE(wm8994_devs),
|
||||
NULL, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(wm8994->dev, "Failed to add children: %d\n", ret);
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_enable:
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
err_get:
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies);
|
||||
err_supplies:
|
||||
kfree(wm8994->supplies);
|
||||
err:
|
||||
mfd_remove_devices(wm8994->dev);
|
||||
kfree(wm8994);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8994_device_exit(struct wm8994 *wm8994)
|
||||
{
|
||||
mfd_remove_devices(wm8994->dev);
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies),
|
||||
wm8994->supplies);
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies);
|
||||
kfree(wm8994->supplies);
|
||||
kfree(wm8994);
|
||||
}
|
||||
|
||||
static int wm8994_i2c_read_device(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *dest)
|
||||
{
|
||||
struct i2c_client *i2c = wm8994->control_data;
|
||||
int ret;
|
||||
u16 r = cpu_to_be16(reg);
|
||||
|
||||
ret = i2c_master_send(i2c, (unsigned char *)&r, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != 2)
|
||||
return -EIO;
|
||||
|
||||
ret = i2c_master_recv(i2c, dest, bytes);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != bytes)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Currently we allocate the write buffer on the stack; this is OK for
|
||||
* small writes - if we need to do large writes this will need to be
|
||||
* revised.
|
||||
*/
|
||||
static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *src)
|
||||
{
|
||||
struct i2c_client *i2c = wm8994->control_data;
|
||||
unsigned char msg[bytes + 2];
|
||||
int ret;
|
||||
|
||||
reg = cpu_to_be16(reg);
|
||||
memcpy(&msg[0], ®, 2);
|
||||
memcpy(&msg[2], src, bytes);
|
||||
|
||||
ret = i2c_master_send(i2c, msg, bytes + 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < bytes + 2)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8994_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8994 *wm8994;
|
||||
|
||||
wm8994 = kzalloc(sizeof(struct wm8994), GFP_KERNEL);
|
||||
if (wm8994 == NULL) {
|
||||
kfree(i2c);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(i2c, wm8994);
|
||||
wm8994->dev = &i2c->dev;
|
||||
wm8994->control_data = i2c;
|
||||
wm8994->read_dev = wm8994_i2c_read_device;
|
||||
wm8994->write_dev = wm8994_i2c_write_device;
|
||||
|
||||
return wm8994_device_init(wm8994, id->driver_data, i2c->irq);
|
||||
}
|
||||
|
||||
static int wm8994_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct wm8994 *wm8994 = i2c_get_clientdata(i2c);
|
||||
|
||||
wm8994_device_exit(wm8994);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8994_i2c_suspend(struct i2c_client *i2c, pm_message_t state)
|
||||
{
|
||||
return wm8994_device_suspend(&i2c->dev);
|
||||
}
|
||||
|
||||
static int wm8994_i2c_resume(struct i2c_client *i2c)
|
||||
{
|
||||
return wm8994_device_resume(&i2c->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8994_i2c_suspend NULL
|
||||
#define wm8994_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8994_i2c_id[] = {
|
||||
{ "wm8994", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8994_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8994_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "wm8994",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8994_i2c_probe,
|
||||
.remove = wm8994_i2c_remove,
|
||||
.suspend = wm8994_i2c_suspend,
|
||||
.resume = wm8994_i2c_resume,
|
||||
.id_table = wm8994_i2c_id,
|
||||
};
|
||||
|
||||
static int __init wm8994_i2c_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&wm8994_i2c_driver);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to register wm8994 I2C driver: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(wm8994_i2c_init);
|
||||
|
||||
static void __exit wm8994_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wm8994_i2c_driver);
|
||||
}
|
||||
module_exit(wm8994_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Core support for the WM8994 audio CODEC");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
|
@ -365,7 +365,7 @@ config MMC_SDRICOH_CS
|
|||
|
||||
config MMC_TMIO
|
||||
tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support"
|
||||
depends on MFD_TMIO || MFD_ASIC3 || SUPERH
|
||||
depends on MFD_TMIO || MFD_ASIC3 || MFD_SH_MOBILE_SDHI
|
||||
help
|
||||
This provides support for the SD/MMC cell found in TC6393XB,
|
||||
T7L66XB and also HTC ASIC3
|
||||
|
|
|
@ -323,7 +323,7 @@ static irqreturn_t tmio_mmc_irq(int irq, void *devid)
|
|||
if (ireg & (TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE)) {
|
||||
ack_mmc_irqs(host, TMIO_STAT_CARD_INSERT |
|
||||
TMIO_STAT_CARD_REMOVE);
|
||||
mmc_detect_change(host->mmc, 0);
|
||||
mmc_detect_change(host->mmc, msecs_to_jiffies(100));
|
||||
}
|
||||
|
||||
/* CRC and other errors */
|
||||
|
@ -550,6 +550,7 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
|
|||
|
||||
mmc->ops = &tmio_mmc_ops;
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA;
|
||||
mmc->caps |= pdata->capabilities;
|
||||
mmc->f_max = pdata->hclk;
|
||||
mmc->f_min = mmc->f_max / 512;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
|
@ -568,14 +569,14 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
|
|||
if (ret >= 0)
|
||||
host->irq = ret;
|
||||
else
|
||||
goto unmap_ctl;
|
||||
goto cell_disable;
|
||||
|
||||
disable_mmc_irqs(host, TMIO_MASK_ALL);
|
||||
|
||||
ret = request_irq(host->irq, tmio_mmc_irq, IRQF_DISABLED |
|
||||
IRQF_TRIGGER_FALLING, dev_name(&dev->dev), host);
|
||||
if (ret)
|
||||
goto unmap_ctl;
|
||||
goto cell_disable;
|
||||
|
||||
mmc_add_host(mmc);
|
||||
|
||||
|
@ -587,6 +588,9 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
|
|||
|
||||
return 0;
|
||||
|
||||
cell_disable:
|
||||
if (cell->disable)
|
||||
cell->disable(dev);
|
||||
unmap_ctl:
|
||||
iounmap(host->ctl);
|
||||
host_free:
|
||||
|
@ -597,6 +601,7 @@ out:
|
|||
|
||||
static int __devexit tmio_mmc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data;
|
||||
struct mmc_host *mmc = platform_get_drvdata(dev);
|
||||
|
||||
platform_set_drvdata(dev, NULL);
|
||||
|
@ -605,6 +610,8 @@ static int __devexit tmio_mmc_remove(struct platform_device *dev)
|
|||
struct tmio_mmc_host *host = mmc_priv(mmc);
|
||||
mmc_remove_host(mmc);
|
||||
free_irq(host->irq, host);
|
||||
if (cell->disable)
|
||||
cell->disable(dev);
|
||||
iounmap(host->ctl);
|
||||
mmc_free_host(mmc);
|
||||
}
|
||||
|
|
|
@ -55,10 +55,8 @@
|
|||
/* Define some IRQ masks */
|
||||
/* This is the mask used at reset by the chip */
|
||||
#define TMIO_MASK_ALL 0x837f031d
|
||||
#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND | \
|
||||
TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT)
|
||||
#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND | \
|
||||
TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT)
|
||||
#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND)
|
||||
#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND)
|
||||
#define TMIO_MASK_CMD (TMIO_STAT_CMDRESPEND | TMIO_STAT_CMDTIMEOUT | \
|
||||
TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT)
|
||||
#define TMIO_MASK_IRQ (TMIO_MASK_READOP | TMIO_MASK_WRITEOP | TMIO_MASK_CMD)
|
||||
|
|
|
@ -29,6 +29,13 @@ config APM_POWER
|
|||
Say Y here to enable support APM status emulation using
|
||||
battery class devices.
|
||||
|
||||
config MAX8925_POWER
|
||||
tristate "MAX8925 battery charger support"
|
||||
depends on MFD_MAX8925
|
||||
help
|
||||
Say Y here to enable support for the battery charger in the Maxim
|
||||
MAX8925 PMIC.
|
||||
|
||||
config WM831X_BACKUP
|
||||
tristate "WM831X backup battery charger support"
|
||||
depends on MFD_WM831X
|
||||
|
|
|
@ -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_MAX8925_POWER) += max8925_power.o
|
||||
obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
|
||||
obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
|
||||
obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
|
||||
|
|
|
@ -0,0 +1,534 @@
|
|||
/*
|
||||
* Battery driver for Maxim MAX8925
|
||||
*
|
||||
* Copyright (c) 2009-2010 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
/* registers in GPM */
|
||||
#define MAX8925_OUT5VEN 0x54
|
||||
#define MAX8925_OUT3VEN 0x58
|
||||
#define MAX8925_CHG_CNTL1 0x7c
|
||||
|
||||
/* bits definition */
|
||||
#define MAX8925_CHG_STAT_VSYSLOW (1 << 0)
|
||||
#define MAX8925_CHG_STAT_MODE_MASK (3 << 2)
|
||||
#define MAX8925_CHG_STAT_EN_MASK (1 << 4)
|
||||
#define MAX8925_CHG_MBDET (1 << 1)
|
||||
#define MAX8925_CHG_AC_RANGE_MASK (3 << 6)
|
||||
|
||||
/* registers in ADC */
|
||||
#define MAX8925_ADC_RES_CNFG1 0x06
|
||||
#define MAX8925_ADC_AVG_CNFG1 0x07
|
||||
#define MAX8925_ADC_ACQ_CNFG1 0x08
|
||||
#define MAX8925_ADC_ACQ_CNFG2 0x09
|
||||
/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */
|
||||
#define MAX8925_ADC_AUX2 0x62
|
||||
#define MAX8925_ADC_VCHG 0x64
|
||||
#define MAX8925_ADC_VBBATT 0x66
|
||||
#define MAX8925_ADC_VMBATT 0x68
|
||||
#define MAX8925_ADC_ISNS 0x6a
|
||||
#define MAX8925_ADC_THM 0x6c
|
||||
#define MAX8925_ADC_TDIE 0x6e
|
||||
#define MAX8925_CMD_AUX2 0xc8
|
||||
#define MAX8925_CMD_VCHG 0xd0
|
||||
#define MAX8925_CMD_VBBATT 0xd8
|
||||
#define MAX8925_CMD_VMBATT 0xe0
|
||||
#define MAX8925_CMD_ISNS 0xe8
|
||||
#define MAX8925_CMD_THM 0xf0
|
||||
#define MAX8925_CMD_TDIE 0xf8
|
||||
|
||||
enum {
|
||||
MEASURE_AUX2,
|
||||
MEASURE_VCHG,
|
||||
MEASURE_VBBATT,
|
||||
MEASURE_VMBATT,
|
||||
MEASURE_ISNS,
|
||||
MEASURE_THM,
|
||||
MEASURE_TDIE,
|
||||
MEASURE_MAX,
|
||||
};
|
||||
|
||||
struct max8925_power_info {
|
||||
struct max8925_chip *chip;
|
||||
struct i2c_client *gpm;
|
||||
struct i2c_client *adc;
|
||||
|
||||
struct power_supply ac;
|
||||
struct power_supply usb;
|
||||
struct power_supply battery;
|
||||
int irq_base;
|
||||
unsigned ac_online:1;
|
||||
unsigned usb_online:1;
|
||||
unsigned bat_online:1;
|
||||
unsigned chg_mode:2;
|
||||
unsigned batt_detect:1; /* detecing MB by ID pin */
|
||||
unsigned topoff_threshold:2;
|
||||
unsigned fast_charge:3;
|
||||
|
||||
int (*set_charger) (int);
|
||||
};
|
||||
|
||||
static int __set_charger(struct max8925_power_info *info, int enable)
|
||||
{
|
||||
struct max8925_chip *chip = info->chip;
|
||||
if (enable) {
|
||||
/* enable charger in platform */
|
||||
if (info->set_charger)
|
||||
info->set_charger(1);
|
||||
/* enable charger */
|
||||
max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0);
|
||||
} else {
|
||||
/* disable charge */
|
||||
max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
|
||||
if (info->set_charger)
|
||||
info->set_charger(0);
|
||||
}
|
||||
dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger"
|
||||
: "Disable charger");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t max8925_charger_handler(int irq, void *data)
|
||||
{
|
||||
struct max8925_power_info *info = (struct max8925_power_info *)data;
|
||||
struct max8925_chip *chip = info->chip;
|
||||
|
||||
switch (irq - chip->irq_base) {
|
||||
case MAX8925_IRQ_VCHG_DC_R:
|
||||
info->ac_online = 1;
|
||||
__set_charger(info, 1);
|
||||
dev_dbg(chip->dev, "Adapter inserted\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_DC_F:
|
||||
info->ac_online = 0;
|
||||
__set_charger(info, 0);
|
||||
dev_dbg(chip->dev, "Adapter is removal\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_USB_R:
|
||||
info->usb_online = 1;
|
||||
__set_charger(info, 1);
|
||||
dev_dbg(chip->dev, "USB inserted\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_USB_F:
|
||||
info->usb_online = 0;
|
||||
__set_charger(info, 0);
|
||||
dev_dbg(chip->dev, "USB is removal\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_THM_OK_F:
|
||||
/* Battery is not ready yet */
|
||||
dev_dbg(chip->dev, "Battery temperature is out of range\n");
|
||||
case MAX8925_IRQ_VCHG_DC_OVP:
|
||||
dev_dbg(chip->dev, "Error detection\n");
|
||||
__set_charger(info, 0);
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_THM_OK_R:
|
||||
/* Battery is ready now */
|
||||
dev_dbg(chip->dev, "Battery temperature is in range\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_SYSLOW_R:
|
||||
/* VSYS is low */
|
||||
dev_info(chip->dev, "Sys power is too low\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_SYSLOW_F:
|
||||
dev_dbg(chip->dev, "Sys power is above low threshold\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_DONE:
|
||||
__set_charger(info, 0);
|
||||
dev_dbg(chip->dev, "Charging is done\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_TOPOFF:
|
||||
dev_dbg(chip->dev, "Charging in top-off mode\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_TMR_FAULT:
|
||||
__set_charger(info, 0);
|
||||
dev_dbg(chip->dev, "Safe timer is expired\n");
|
||||
break;
|
||||
case MAX8925_IRQ_VCHG_RST:
|
||||
__set_charger(info, 0);
|
||||
dev_dbg(chip->dev, "Charger is reset\n");
|
||||
break;
|
||||
}
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int start_measure(struct max8925_power_info *info, int type)
|
||||
{
|
||||
unsigned char buf[2] = {0, 0};
|
||||
int meas_reg = 0, ret;
|
||||
|
||||
switch (type) {
|
||||
case MEASURE_VCHG:
|
||||
meas_reg = MAX8925_ADC_VCHG;
|
||||
break;
|
||||
case MEASURE_VBBATT:
|
||||
meas_reg = MAX8925_ADC_VBBATT;
|
||||
break;
|
||||
case MEASURE_VMBATT:
|
||||
meas_reg = MAX8925_ADC_VMBATT;
|
||||
break;
|
||||
case MEASURE_ISNS:
|
||||
meas_reg = MAX8925_ADC_ISNS;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
max8925_bulk_read(info->adc, meas_reg, 2, buf);
|
||||
ret = (buf[0] << 4) | (buf[1] >> 4);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8925_ac_get_prop(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = info->ac_online;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
if (info->ac_online) {
|
||||
ret = start_measure(info, MEASURE_VCHG);
|
||||
if (ret >= 0) {
|
||||
val->intval = ret << 1; /* unit is mV */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property max8925_ac_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
};
|
||||
|
||||
static int max8925_usb_get_prop(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = info->usb_online;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
if (info->usb_online) {
|
||||
ret = start_measure(info, MEASURE_VCHG);
|
||||
if (ret >= 0) {
|
||||
val->intval = ret << 1; /* unit is mV */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property max8925_usb_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
};
|
||||
|
||||
static int max8925_bat_get_prop(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
|
||||
long long int tmp = 0;
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = info->bat_online;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
if (info->bat_online) {
|
||||
ret = start_measure(info, MEASURE_VMBATT);
|
||||
if (ret >= 0) {
|
||||
val->intval = ret << 1; /* unit is mV */
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
if (info->bat_online) {
|
||||
ret = start_measure(info, MEASURE_ISNS);
|
||||
if (ret >= 0) {
|
||||
tmp = (long long int)ret * 6250 / 4096 - 3125;
|
||||
ret = (int)tmp;
|
||||
val->intval = 0;
|
||||
if (ret > 0)
|
||||
val->intval = ret; /* unit is mA */
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||||
if (!info->bat_online) {
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
|
||||
ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2;
|
||||
switch (ret) {
|
||||
case 1:
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||||
break;
|
||||
case 0:
|
||||
case 2:
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
||||
break;
|
||||
case 3:
|
||||
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||||
break;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!info->bat_online) {
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
|
||||
if (info->usb_online || info->ac_online) {
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
if (ret & MAX8925_CHG_STAT_EN_MASK)
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
} else
|
||||
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property max8925_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
};
|
||||
|
||||
#define REQUEST_IRQ(_irq, _name) \
|
||||
do { \
|
||||
ret = request_threaded_irq(chip->irq_base + _irq, NULL, \
|
||||
max8925_charger_handler, \
|
||||
IRQF_ONESHOT, _name, info); \
|
||||
if (ret) \
|
||||
dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \
|
||||
_irq, ret); \
|
||||
} while (0)
|
||||
|
||||
static __devinit int max8925_init_charger(struct max8925_chip *chip,
|
||||
struct max8925_power_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff");
|
||||
REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire");
|
||||
|
||||
info->ac_online = 0;
|
||||
info->usb_online = 0;
|
||||
info->bat_online = 0;
|
||||
ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
|
||||
if (ret >= 0) {
|
||||
/*
|
||||
* If battery detection is enabled, ID pin of battery is
|
||||
* connected to MBDET pin of MAX8925. It could be used to
|
||||
* detect battery presence.
|
||||
* Otherwise, we have to assume that battery is always on.
|
||||
*/
|
||||
if (info->batt_detect)
|
||||
info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1;
|
||||
else
|
||||
info->bat_online = 1;
|
||||
if (ret & MAX8925_CHG_AC_RANGE_MASK)
|
||||
info->ac_online = 1;
|
||||
else
|
||||
info->ac_online = 0;
|
||||
}
|
||||
/* disable charge */
|
||||
max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
|
||||
/* set charging current in charge topoff mode */
|
||||
max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5,
|
||||
info->topoff_threshold << 5);
|
||||
/* set charing current in fast charge mode */
|
||||
max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __devexit int max8925_deinit_charger(struct max8925_power_info *info)
|
||||
{
|
||||
struct max8925_chip *chip = info->chip;
|
||||
int irq;
|
||||
|
||||
irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP;
|
||||
for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++)
|
||||
free_irq(irq, info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __devinit int max8925_power_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct max8925_platform_data *max8925_pdata;
|
||||
struct max8925_power_pdata *pdata = NULL;
|
||||
struct max8925_power_info *info;
|
||||
int ret;
|
||||
|
||||
if (pdev->dev.parent->platform_data) {
|
||||
max8925_pdata = pdev->dev.parent->platform_data;
|
||||
pdata = max8925_pdata->power;
|
||||
}
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform data isn't assigned to "
|
||||
"power supply\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
info->chip = chip;
|
||||
info->gpm = chip->i2c;
|
||||
info->adc = chip->adc;
|
||||
|
||||
info->ac.name = "max8925-ac";
|
||||
info->ac.type = POWER_SUPPLY_TYPE_MAINS;
|
||||
info->ac.properties = max8925_ac_props;
|
||||
info->ac.num_properties = ARRAY_SIZE(max8925_ac_props);
|
||||
info->ac.get_property = max8925_ac_get_prop;
|
||||
ret = power_supply_register(&pdev->dev, &info->ac);
|
||||
if (ret)
|
||||
goto out;
|
||||
info->ac.dev->parent = &pdev->dev;
|
||||
|
||||
info->usb.name = "max8925-usb";
|
||||
info->usb.type = POWER_SUPPLY_TYPE_USB;
|
||||
info->usb.properties = max8925_usb_props;
|
||||
info->usb.num_properties = ARRAY_SIZE(max8925_usb_props);
|
||||
info->usb.get_property = max8925_usb_get_prop;
|
||||
ret = power_supply_register(&pdev->dev, &info->usb);
|
||||
if (ret)
|
||||
goto out_usb;
|
||||
info->usb.dev->parent = &pdev->dev;
|
||||
|
||||
info->battery.name = "max8925-battery";
|
||||
info->battery.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
info->battery.properties = max8925_battery_props;
|
||||
info->battery.num_properties = ARRAY_SIZE(max8925_battery_props);
|
||||
info->battery.get_property = max8925_bat_get_prop;
|
||||
ret = power_supply_register(&pdev->dev, &info->battery);
|
||||
if (ret)
|
||||
goto out_battery;
|
||||
info->battery.dev->parent = &pdev->dev;
|
||||
|
||||
info->batt_detect = pdata->batt_detect;
|
||||
info->topoff_threshold = pdata->topoff_threshold;
|
||||
info->fast_charge = pdata->fast_charge;
|
||||
info->set_charger = pdata->set_charger;
|
||||
dev_set_drvdata(&pdev->dev, info);
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
max8925_init_charger(chip, info);
|
||||
return 0;
|
||||
out_battery:
|
||||
power_supply_unregister(&info->battery);
|
||||
out_usb:
|
||||
power_supply_unregister(&info->ac);
|
||||
out:
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit int max8925_power_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_power_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
if (info) {
|
||||
power_supply_unregister(&info->ac);
|
||||
power_supply_unregister(&info->usb);
|
||||
power_supply_unregister(&info->battery);
|
||||
max8925_deinit_charger(info);
|
||||
kfree(info);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max8925_power_driver = {
|
||||
.probe = max8925_power_probe,
|
||||
.remove = __devexit_p(max8925_power_remove),
|
||||
.driver = {
|
||||
.name = "max8925-power",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init max8925_power_init(void)
|
||||
{
|
||||
return platform_driver_register(&max8925_power_driver);
|
||||
}
|
||||
module_init(max8925_power_init);
|
||||
|
||||
static void __exit max8925_power_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&max8925_power_driver);
|
||||
}
|
||||
module_exit(max8925_power_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Power supply driver for MAX8925");
|
||||
MODULE_ALIAS("platform:max8925-power");
|
|
@ -190,7 +190,7 @@ static irqreturn_t wm8350_charger_handler(int irq, void *data)
|
|||
struct wm8350_power *power = &wm8350->power;
|
||||
struct wm8350_charger_policy *policy = power->policy;
|
||||
|
||||
switch (irq) {
|
||||
switch (irq - wm8350->irq_base) {
|
||||
case WM8350_IRQ_CHG_BAT_FAIL:
|
||||
dev_err(wm8350->dev, "battery failed\n");
|
||||
break;
|
||||
|
@ -428,18 +428,18 @@ static void wm8350_init_charger(struct wm8350 *wm8350)
|
|||
|
||||
static void free_charger_irq(struct wm8350 *wm8350)
|
||||
{
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350);
|
||||
}
|
||||
|
||||
static __devinit int wm8350_power_probe(struct platform_device *pdev)
|
||||
|
|
|
@ -11,15 +11,17 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/init.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/mfd/88pm8607.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
struct pm8607_regulator_info {
|
||||
struct regulator_desc desc;
|
||||
struct pm8607_chip *chip;
|
||||
struct pm860x_chip *chip;
|
||||
struct regulator_dev *regulator;
|
||||
struct i2c_client *i2c;
|
||||
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
|
@ -46,7 +48,6 @@ static inline int check_range(struct pm8607_regulator_info *info,
|
|||
static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
uint8_t chip_id = info->chip->chip_id;
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (info->desc.id) {
|
||||
|
@ -88,79 +89,29 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index)
|
|||
case PM8607_ID_LDO2:
|
||||
case PM8607_ID_LDO3:
|
||||
case PM8607_ID_LDO9:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 8) ? (index * 50000 + 2550000) :
|
||||
-EINVAL);
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2550000) :
|
||||
3300000);
|
||||
break;
|
||||
}
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2550000) :
|
||||
3300000);
|
||||
break;
|
||||
case PM8607_ID_LDO4:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 8) ? (index * 50000 + 2550000) :
|
||||
-EINVAL);
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 6) ? (index * 50000 + 2550000) :
|
||||
((index == 6) ? 2900000 : 3300000));
|
||||
break;
|
||||
}
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 6) ? (index * 50000 + 2550000) :
|
||||
((index == 6) ? 2900000 : 3300000));
|
||||
break;
|
||||
case PM8607_ID_LDO6:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 8) ? (index * 50000 + 2450000) :
|
||||
-EINVAL);
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
ret = (index < 2) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2500000) :
|
||||
3300000);
|
||||
break;
|
||||
}
|
||||
ret = (index < 2) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2500000) :
|
||||
3300000);
|
||||
break;
|
||||
case PM8607_ID_LDO10:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 8) ? (index * 50000 + 2550000) :
|
||||
1200000);
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2550000) :
|
||||
((index == 7) ? 3300000 : 1200000));
|
||||
break;
|
||||
}
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2550000) :
|
||||
((index == 7) ? 3300000 : 1200000));
|
||||
break;
|
||||
case PM8607_ID_LDO14:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
ret = (index < 3) ? (index * 50000 + 1800000) :
|
||||
((index < 8) ? (index * 50000 + 2550000) :
|
||||
-EINVAL);
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
ret = (index < 2) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2600000) :
|
||||
3300000);
|
||||
break;
|
||||
}
|
||||
ret = (index < 2) ? (index * 50000 + 1800000) :
|
||||
((index < 7) ? (index * 50000 + 2600000) :
|
||||
3300000);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
|
@ -169,7 +120,6 @@ static int pm8607_list_voltage(struct regulator_dev *rdev, unsigned index)
|
|||
static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
uint8_t chip_id = info->chip->chip_id;
|
||||
int val = -ENOENT;
|
||||
int ret;
|
||||
|
||||
|
@ -254,161 +204,77 @@ static int choose_voltage(struct regulator_dev *rdev, int min_uV, int max_uV)
|
|||
case PM8607_ID_LDO2:
|
||||
case PM8607_ID_LDO3:
|
||||
case PM8607_ID_LDO9:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
if (min_uV < 2700000) /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
else { /* 2700mV ~ 2900mV / 50mV */
|
||||
if (min_uV <= 2900000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2850mV / 50mV */
|
||||
if (min_uV <= 2850000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2850mV / 50mV */
|
||||
if (min_uV <= 2850000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_ID_LDO4:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
if (min_uV < 2700000) /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
else { /* 2700mV ~ 2900mV / 50mV */
|
||||
if (min_uV <= 2900000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2800mV / 50mV */
|
||||
if (min_uV <= 2850000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else if (min_uV <= 2900000)
|
||||
val = 6;
|
||||
else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2800mV / 50mV */
|
||||
if (min_uV <= 2850000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else if (min_uV <= 2900000)
|
||||
val = 6;
|
||||
else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_ID_LDO6:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
if (min_uV < 2600000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2600mV */
|
||||
} else { /* 2600mV ~ 2800mV / 50mV */
|
||||
if (min_uV <= 2800000) {
|
||||
val = (min_uV - 2550001) / 50000;
|
||||
val += 3;
|
||||
} else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
if (min_uV < 2600000) { /* 1800mV ~ 1850mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1850000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 2; /* 2600mV */
|
||||
} else { /* 2600mV ~ 2800mV / 50mV */
|
||||
if (min_uV <= 2800000) {
|
||||
val = (min_uV - 2550001) / 50000;
|
||||
val += 2;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
if (min_uV < 2600000) { /* 1800mV ~ 1850mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1850000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 2; /* 2600mV */
|
||||
} else { /* 2600mV ~ 2800mV / 50mV */
|
||||
if (min_uV <= 2800000) {
|
||||
val = (min_uV - 2550001) / 50000;
|
||||
val += 2;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_ID_LDO14:
|
||||
switch (chip_id) {
|
||||
case PM8607_CHIP_A0:
|
||||
case PM8607_CHIP_A1:
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1900mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1900000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 3; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2900mV / 50mV */
|
||||
if (min_uV <= 2900000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 3;
|
||||
} else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PM8607_CHIP_B0:
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1850mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1850000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 2; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2900mV / 50mV */
|
||||
if (min_uV <= 2900000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 2;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
if (min_uV < 2700000) { /* 1800mV ~ 1850mV / 50mV */
|
||||
if (min_uV <= 1800000)
|
||||
val = 0;
|
||||
else if (min_uV <= 1850000)
|
||||
val = (min_uV - 1750001) / 50000;
|
||||
else
|
||||
val = 2; /* 2700mV */
|
||||
} else { /* 2700mV ~ 2900mV / 50mV */
|
||||
if (min_uV <= 2900000) {
|
||||
val = (min_uV - 2650001) / 50000;
|
||||
val += 2;
|
||||
} else if (min_uV <= 3300000)
|
||||
val = 7;
|
||||
else
|
||||
val = -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -428,7 +294,6 @@ static int pm8607_set_voltage(struct regulator_dev *rdev,
|
|||
int min_uV, int max_uV)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
struct pm8607_chip *chip = info->chip;
|
||||
uint8_t val, mask;
|
||||
int ret;
|
||||
|
||||
|
@ -443,13 +308,13 @@ static int pm8607_set_voltage(struct regulator_dev *rdev,
|
|||
val = (uint8_t)(ret << info->vol_shift);
|
||||
mask = ((1 << info->vol_nbits) - 1) << info->vol_shift;
|
||||
|
||||
ret = pm8607_set_bits(chip, info->vol_reg, mask, val);
|
||||
ret = pm860x_set_bits(info->i2c, info->vol_reg, mask, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
switch (info->desc.id) {
|
||||
case PM8607_ID_BUCK1:
|
||||
case PM8607_ID_BUCK3:
|
||||
ret = pm8607_set_bits(chip, info->update_reg,
|
||||
ret = pm860x_set_bits(info->i2c, info->update_reg,
|
||||
1 << info->update_bit,
|
||||
1 << info->update_bit);
|
||||
break;
|
||||
|
@ -460,11 +325,10 @@ static int pm8607_set_voltage(struct regulator_dev *rdev,
|
|||
static int pm8607_get_voltage(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
struct pm8607_chip *chip = info->chip;
|
||||
uint8_t val, mask;
|
||||
int ret;
|
||||
|
||||
ret = pm8607_reg_read(chip, info->vol_reg);
|
||||
ret = pm860x_reg_read(info->i2c, info->vol_reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -477,9 +341,8 @@ static int pm8607_get_voltage(struct regulator_dev *rdev)
|
|||
static int pm8607_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
struct pm8607_chip *chip = info->chip;
|
||||
|
||||
return pm8607_set_bits(chip, info->enable_reg,
|
||||
return pm860x_set_bits(info->i2c, info->enable_reg,
|
||||
1 << info->enable_bit,
|
||||
1 << info->enable_bit);
|
||||
}
|
||||
|
@ -487,19 +350,17 @@ static int pm8607_enable(struct regulator_dev *rdev)
|
|||
static int pm8607_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
struct pm8607_chip *chip = info->chip;
|
||||
|
||||
return pm8607_set_bits(chip, info->enable_reg,
|
||||
return pm860x_set_bits(info->i2c, info->enable_reg,
|
||||
1 << info->enable_bit, 0);
|
||||
}
|
||||
|
||||
static int pm8607_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pm8607_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
struct pm8607_chip *chip = info->chip;
|
||||
int ret;
|
||||
|
||||
ret = pm8607_reg_read(chip, info->enable_reg);
|
||||
ret = pm860x_reg_read(info->i2c, info->enable_reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -589,8 +450,8 @@ static inline struct pm8607_regulator_info *find_regulator_info(int id)
|
|||
|
||||
static int __devinit pm8607_regulator_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm8607_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm8607_platform_data *pdata = chip->dev->platform_data;
|
||||
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm860x_platform_data *pdata = chip->dev->platform_data;
|
||||
struct pm8607_regulator_info *info = NULL;
|
||||
|
||||
info = find_regulator_info(pdev->id);
|
||||
|
@ -599,6 +460,7 @@ static int __devinit pm8607_regulator_probe(struct platform_device *pdev)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
|
||||
info->chip = chip;
|
||||
|
||||
info->regulator = regulator_register(&info->desc, &pdev->dev,
|
||||
|
|
|
@ -94,6 +94,12 @@ config REGULATOR_MAX8660
|
|||
This driver controls a Maxim 8660/8661 voltage output
|
||||
regulator via I2C bus.
|
||||
|
||||
config REGULATOR_MAX8925
|
||||
tristate "Maxim MAX8925 Power Management IC"
|
||||
depends on MFD_MAX8925
|
||||
help
|
||||
Say y here to support the voltage regulaltor of Maxim MAX8925 PMIC.
|
||||
|
||||
config REGULATOR_TWL4030
|
||||
bool "TI TWL4030/TWL5030/TWL6030/TPS695x0 PMIC"
|
||||
depends on TWL4030_CORE
|
||||
|
@ -191,7 +197,7 @@ config REGULATOR_TPS6507X
|
|||
|
||||
config REGULATOR_88PM8607
|
||||
bool "Marvell 88PM8607 Power regulators"
|
||||
depends on MFD_88PM8607=y
|
||||
depends on MFD_88PM860X=y
|
||||
help
|
||||
This driver supports 88PM8607 voltage regulator chips.
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
|
|||
obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
|
||||
obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
|
||||
obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
|
||||
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
|
||||
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Regulators driver for Maxim max8925
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
#define SD1_DVM_VMIN 850000
|
||||
#define SD1_DVM_VMAX 1000000
|
||||
#define SD1_DVM_STEP 50000
|
||||
#define SD1_DVM_SHIFT 5 /* SDCTL1 bit5 */
|
||||
#define SD1_DVM_EN 6 /* SDV1 bit 6 */
|
||||
|
||||
struct max8925_regulator_info {
|
||||
struct regulator_desc desc;
|
||||
struct regulator_dev *regulator;
|
||||
struct i2c_client *i2c;
|
||||
struct max8925_chip *chip;
|
||||
|
||||
int min_uV;
|
||||
int max_uV;
|
||||
int step_uV;
|
||||
int vol_reg;
|
||||
int vol_shift;
|
||||
int vol_nbits;
|
||||
int enable_bit;
|
||||
int enable_reg;
|
||||
};
|
||||
|
||||
static inline int check_range(struct max8925_regulator_info *info,
|
||||
int min_uV, int max_uV)
|
||||
{
|
||||
if (min_uV < info->min_uV || min_uV > info->max_uV)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max8925_list_voltage(struct regulator_dev *rdev, unsigned index)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
return info->min_uV + index * info->step_uV;
|
||||
}
|
||||
|
||||
static int max8925_set_voltage(struct regulator_dev *rdev,
|
||||
int min_uV, int max_uV)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
unsigned char data, mask;
|
||||
|
||||
if (check_range(info, min_uV, max_uV)) {
|
||||
dev_err(info->chip->dev, "invalid voltage range (%d, %d) uV\n",
|
||||
min_uV, max_uV);
|
||||
return -EINVAL;
|
||||
}
|
||||
data = (min_uV - info->min_uV + info->step_uV - 1) / info->step_uV;
|
||||
data <<= info->vol_shift;
|
||||
mask = ((1 << info->vol_nbits) - 1) << info->vol_shift;
|
||||
|
||||
return max8925_set_bits(info->i2c, info->vol_reg, mask, data);
|
||||
}
|
||||
|
||||
static int max8925_get_voltage(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
unsigned char data, mask;
|
||||
int ret;
|
||||
|
||||
ret = max8925_reg_read(info->i2c, info->vol_reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mask = ((1 << info->vol_nbits) - 1) << info->vol_shift;
|
||||
data = (ret & mask) >> info->vol_shift;
|
||||
|
||||
return max8925_list_voltage(rdev, data);
|
||||
}
|
||||
|
||||
static int max8925_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
|
||||
return max8925_set_bits(info->i2c, info->enable_reg,
|
||||
1 << info->enable_bit,
|
||||
1 << info->enable_bit);
|
||||
}
|
||||
|
||||
static int max8925_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
|
||||
return max8925_set_bits(info->i2c, info->enable_reg,
|
||||
1 << info->enable_bit, 0);
|
||||
}
|
||||
|
||||
static int max8925_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
int ret;
|
||||
|
||||
ret = max8925_reg_read(info->i2c, info->vol_reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret & (1 << info->enable_bit);
|
||||
}
|
||||
|
||||
static int max8925_set_dvm_voltage(struct regulator_dev *rdev, int uV)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
unsigned char data, mask;
|
||||
|
||||
if (uV < SD1_DVM_VMIN || uV > SD1_DVM_VMAX)
|
||||
return -EINVAL;
|
||||
|
||||
data = (uV - SD1_DVM_VMIN + SD1_DVM_STEP - 1) / SD1_DVM_STEP;
|
||||
data <<= SD1_DVM_SHIFT;
|
||||
mask = 3 << SD1_DVM_SHIFT;
|
||||
|
||||
return max8925_set_bits(info->i2c, info->enable_reg, mask, data);
|
||||
}
|
||||
|
||||
static int max8925_set_dvm_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
|
||||
return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN,
|
||||
1 << SD1_DVM_EN);
|
||||
}
|
||||
|
||||
static int max8925_set_dvm_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct max8925_regulator_info *info = rdev_get_drvdata(rdev);
|
||||
|
||||
return max8925_set_bits(info->i2c, info->vol_reg, 1 << SD1_DVM_EN, 0);
|
||||
}
|
||||
|
||||
static struct regulator_ops max8925_regulator_sdv_ops = {
|
||||
.set_voltage = max8925_set_voltage,
|
||||
.get_voltage = max8925_get_voltage,
|
||||
.enable = max8925_enable,
|
||||
.disable = max8925_disable,
|
||||
.is_enabled = max8925_is_enabled,
|
||||
.set_suspend_voltage = max8925_set_dvm_voltage,
|
||||
.set_suspend_enable = max8925_set_dvm_enable,
|
||||
.set_suspend_disable = max8925_set_dvm_disable,
|
||||
};
|
||||
|
||||
static struct regulator_ops max8925_regulator_ldo_ops = {
|
||||
.set_voltage = max8925_set_voltage,
|
||||
.get_voltage = max8925_get_voltage,
|
||||
.enable = max8925_enable,
|
||||
.disable = max8925_disable,
|
||||
.is_enabled = max8925_is_enabled,
|
||||
};
|
||||
|
||||
#define MAX8925_SDV(_id, min, max, step) \
|
||||
{ \
|
||||
.desc = { \
|
||||
.name = "SDV" #_id, \
|
||||
.ops = &max8925_regulator_sdv_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.id = MAX8925_ID_SD##_id, \
|
||||
.owner = THIS_MODULE, \
|
||||
}, \
|
||||
.min_uV = min * 1000, \
|
||||
.max_uV = max * 1000, \
|
||||
.step_uV = step * 1000, \
|
||||
.vol_reg = MAX8925_SDV##_id, \
|
||||
.vol_shift = 0, \
|
||||
.vol_nbits = 6, \
|
||||
.enable_reg = MAX8925_SDCTL##_id, \
|
||||
.enable_bit = 0, \
|
||||
}
|
||||
|
||||
#define MAX8925_LDO(_id, min, max, step) \
|
||||
{ \
|
||||
.desc = { \
|
||||
.name = "LDO" #_id, \
|
||||
.ops = &max8925_regulator_ldo_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.id = MAX8925_ID_LDO##_id, \
|
||||
.owner = THIS_MODULE, \
|
||||
}, \
|
||||
.min_uV = min * 1000, \
|
||||
.max_uV = max * 1000, \
|
||||
.step_uV = step * 1000, \
|
||||
.vol_reg = MAX8925_LDOVOUT##_id, \
|
||||
.vol_shift = 0, \
|
||||
.vol_nbits = 6, \
|
||||
.enable_reg = MAX8925_LDOCTL##_id, \
|
||||
.enable_bit = 0, \
|
||||
}
|
||||
|
||||
static struct max8925_regulator_info max8925_regulator_info[] = {
|
||||
MAX8925_SDV(1, 637.5, 1425, 12.5),
|
||||
MAX8925_SDV(2, 650, 2225, 25),
|
||||
MAX8925_SDV(3, 750, 3900, 50),
|
||||
|
||||
MAX8925_LDO(1, 750, 3900, 50),
|
||||
MAX8925_LDO(2, 650, 2250, 25),
|
||||
MAX8925_LDO(3, 650, 2250, 25),
|
||||
MAX8925_LDO(4, 750, 3900, 50),
|
||||
MAX8925_LDO(5, 750, 3900, 50),
|
||||
MAX8925_LDO(6, 750, 3900, 50),
|
||||
MAX8925_LDO(7, 750, 3900, 50),
|
||||
MAX8925_LDO(8, 750, 3900, 50),
|
||||
MAX8925_LDO(9, 750, 3900, 50),
|
||||
MAX8925_LDO(10, 750, 3900, 50),
|
||||
MAX8925_LDO(11, 750, 3900, 50),
|
||||
MAX8925_LDO(12, 750, 3900, 50),
|
||||
MAX8925_LDO(13, 750, 3900, 50),
|
||||
MAX8925_LDO(14, 750, 3900, 50),
|
||||
MAX8925_LDO(15, 750, 3900, 50),
|
||||
MAX8925_LDO(16, 750, 3900, 50),
|
||||
MAX8925_LDO(17, 650, 2250, 25),
|
||||
MAX8925_LDO(18, 650, 2250, 25),
|
||||
MAX8925_LDO(19, 750, 3900, 50),
|
||||
MAX8925_LDO(20, 750, 3900, 50),
|
||||
};
|
||||
|
||||
static inline struct max8925_regulator_info *find_regulator_info(int id)
|
||||
{
|
||||
struct max8925_regulator_info *ri;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(max8925_regulator_info); i++) {
|
||||
ri = &max8925_regulator_info[i];
|
||||
if (ri->desc.id == id)
|
||||
return ri;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int __devinit max8925_regulator_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct max8925_platform_data *pdata = chip->dev->platform_data;
|
||||
struct max8925_regulator_info *ri = NULL;
|
||||
struct regulator_dev *rdev;
|
||||
|
||||
ri = find_regulator_info(pdev->id);
|
||||
if (ri == NULL) {
|
||||
dev_err(&pdev->dev, "invalid regulator ID specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ri->i2c = chip->i2c;
|
||||
ri->chip = chip;
|
||||
|
||||
rdev = regulator_register(&ri->desc, &pdev->dev,
|
||||
pdata->regulator[pdev->id], ri);
|
||||
if (IS_ERR(rdev)) {
|
||||
dev_err(&pdev->dev, "failed to register regulator %s\n",
|
||||
ri->desc.name);
|
||||
return PTR_ERR(rdev);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, rdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit max8925_regulator_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator_dev *rdev = platform_get_drvdata(pdev);
|
||||
|
||||
regulator_unregister(rdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max8925_regulator_driver = {
|
||||
.driver = {
|
||||
.name = "max8925-regulator",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max8925_regulator_probe,
|
||||
.remove = __devexit_p(max8925_regulator_remove),
|
||||
};
|
||||
|
||||
static int __init max8925_regulator_init(void)
|
||||
{
|
||||
return platform_driver_register(&max8925_regulator_driver);
|
||||
}
|
||||
subsys_initcall(max8925_regulator_init);
|
||||
|
||||
static void __exit max8925_regulator_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&max8925_regulator_driver);
|
||||
}
|
||||
module_exit(max8925_regulator_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_DESCRIPTION("Regulator Driver for Maxim 8925 PMIC");
|
||||
MODULE_ALIAS("platform:max8925-regulator");
|
||||
|
|
@ -1453,7 +1453,7 @@ static int wm8350_regulator_remove(struct platform_device *pdev)
|
|||
struct regulator_dev *rdev = platform_get_drvdata(pdev);
|
||||
struct wm8350 *wm8350 = rdev_get_drvdata(rdev);
|
||||
|
||||
wm8350_free_irq(wm8350, wm8350_reg[pdev->id].irq);
|
||||
wm8350_free_irq(wm8350, wm8350_reg[pdev->id].irq, rdev);
|
||||
|
||||
regulator_unregister(rdev);
|
||||
|
||||
|
|
|
@ -175,6 +175,16 @@ config RTC_DRV_MAX6900
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-max6900.
|
||||
|
||||
config RTC_DRV_MAX8925
|
||||
tristate "Maxim MAX8925"
|
||||
depends on MFD_MAX8925
|
||||
help
|
||||
If you say yes here you will get support for the
|
||||
RTC of Maxim MAX8925 PMIC.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-max8925.
|
||||
|
||||
config RTC_DRV_RS5C372
|
||||
tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A"
|
||||
help
|
||||
|
|
|
@ -52,6 +52,7 @@ obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o
|
|||
obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
|
||||
obj-$(CONFIG_RTC_MXC) += rtc-mxc.o
|
||||
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
|
||||
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
|
||||
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
|
||||
obj-$(CONFIG_RTC_DRV_MC13783) += rtc-mc13783.o
|
||||
obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* RTC driver for Maxim MAX8925
|
||||
*
|
||||
* Copyright (C) 2009-2010 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/i2c.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
enum {
|
||||
RTC_SEC = 0,
|
||||
RTC_MIN,
|
||||
RTC_HOUR,
|
||||
RTC_WEEKDAY,
|
||||
RTC_DATE,
|
||||
RTC_MONTH,
|
||||
RTC_YEAR1,
|
||||
RTC_YEAR2,
|
||||
};
|
||||
|
||||
#define MAX8925_RTC_SEC 0x00
|
||||
#define MAX8925_RTC_MIN 0x01
|
||||
#define MAX8925_RTC_HOUR 0x02
|
||||
#define MAX8925_RTC_WEEKDAY 0x03
|
||||
#define MAX8925_RTC_DATE 0x04
|
||||
#define MAX8925_RTC_MONTH 0x05
|
||||
#define MAX8925_RTC_YEAR1 0x06
|
||||
#define MAX8925_RTC_YEAR2 0x07
|
||||
#define MAX8925_ALARM0_SEC 0x08
|
||||
#define MAX8925_ALARM0_MIN 0x09
|
||||
#define MAX8925_ALARM0_HOUR 0x0a
|
||||
#define MAX8925_ALARM0_WEEKDAY 0x0b
|
||||
#define MAX8925_ALARM0_DATE 0x0c
|
||||
#define MAX8925_ALARM0_MON 0x0d
|
||||
#define MAX8925_ALARM0_YEAR1 0x0e
|
||||
#define MAX8925_ALARM0_YEAR2 0x0f
|
||||
#define MAX8925_ALARM1_SEC 0x10
|
||||
#define MAX8925_ALARM1_MIN 0x11
|
||||
#define MAX8925_ALARM1_HOUR 0x12
|
||||
#define MAX8925_ALARM1_WEEKDAY 0x13
|
||||
#define MAX8925_ALARM1_DATE 0x14
|
||||
#define MAX8925_ALARM1_MON 0x15
|
||||
#define MAX8925_ALARM1_YEAR1 0x16
|
||||
#define MAX8925_ALARM1_YEAR2 0x17
|
||||
#define MAX8925_RTC_CNTL 0x1b
|
||||
#define MAX8925_RTC_STATUS 0x20
|
||||
|
||||
#define TIME_NUM 8
|
||||
#define ALARM_1SEC (1 << 7)
|
||||
#define HOUR_12 (1 << 7)
|
||||
#define HOUR_AM_PM (1 << 5)
|
||||
#define ALARM0_IRQ (1 << 3)
|
||||
#define ALARM1_IRQ (1 << 2)
|
||||
#define ALARM0_STATUS (1 << 2)
|
||||
#define ALARM1_STATUS (1 << 1)
|
||||
|
||||
|
||||
struct max8925_rtc_info {
|
||||
struct rtc_device *rtc_dev;
|
||||
struct max8925_chip *chip;
|
||||
struct i2c_client *rtc;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static irqreturn_t rtc_update_handler(int irq, void *data)
|
||||
{
|
||||
struct max8925_rtc_info *info = (struct max8925_rtc_info *)data;
|
||||
|
||||
/* disable ALARM0 except for 1SEC alarm */
|
||||
max8925_set_bits(info->rtc, MAX8925_ALARM0_CNTL, 0x7f, 0);
|
||||
rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tm_calc(struct rtc_time *tm, unsigned char *buf, int len)
|
||||
{
|
||||
if (len < TIME_NUM)
|
||||
return -EINVAL;
|
||||
tm->tm_year = (buf[RTC_YEAR2] >> 4) * 1000
|
||||
+ (buf[RTC_YEAR2] & 0xf) * 100
|
||||
+ (buf[RTC_YEAR1] >> 4) * 10
|
||||
+ (buf[RTC_YEAR1] & 0xf);
|
||||
tm->tm_year -= 1900;
|
||||
tm->tm_mon = ((buf[RTC_MONTH] >> 4) & 0x01) * 10
|
||||
+ (buf[RTC_MONTH] & 0x0f);
|
||||
tm->tm_mday = ((buf[RTC_DATE] >> 4) & 0x03) * 10
|
||||
+ (buf[RTC_DATE] & 0x0f);
|
||||
tm->tm_wday = buf[RTC_WEEKDAY] & 0x07;
|
||||
if (buf[RTC_HOUR] & HOUR_12) {
|
||||
tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x1) * 10
|
||||
+ (buf[RTC_HOUR] & 0x0f);
|
||||
if (buf[RTC_HOUR] & HOUR_AM_PM)
|
||||
tm->tm_hour += 12;
|
||||
} else
|
||||
tm->tm_hour = ((buf[RTC_HOUR] >> 4) & 0x03) * 10
|
||||
+ (buf[RTC_HOUR] & 0x0f);
|
||||
tm->tm_min = ((buf[RTC_MIN] >> 4) & 0x7) * 10
|
||||
+ (buf[RTC_MIN] & 0x0f);
|
||||
tm->tm_sec = ((buf[RTC_SEC] >> 4) & 0x7) * 10
|
||||
+ (buf[RTC_SEC] & 0x0f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int data_calc(unsigned char *buf, struct rtc_time *tm, int len)
|
||||
{
|
||||
unsigned char high, low;
|
||||
|
||||
if (len < TIME_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
high = (tm->tm_year + 1900) / 1000;
|
||||
low = (tm->tm_year + 1900) / 100;
|
||||
low = low - high * 10;
|
||||
buf[RTC_YEAR2] = (high << 4) + low;
|
||||
high = (tm->tm_year + 1900) / 10;
|
||||
low = tm->tm_year + 1900;
|
||||
low = low - high * 10;
|
||||
high = high - (high / 10) * 10;
|
||||
buf[RTC_YEAR1] = (high << 4) + low;
|
||||
high = tm->tm_mon / 10;
|
||||
low = tm->tm_mon;
|
||||
low = low - high * 10;
|
||||
buf[RTC_MONTH] = (high << 4) + low;
|
||||
high = tm->tm_mday / 10;
|
||||
low = tm->tm_mday;
|
||||
low = low - high * 10;
|
||||
buf[RTC_DATE] = (high << 4) + low;
|
||||
buf[RTC_WEEKDAY] = tm->tm_wday;
|
||||
high = tm->tm_hour / 10;
|
||||
low = tm->tm_hour;
|
||||
low = low - high * 10;
|
||||
buf[RTC_HOUR] = (high << 4) + low;
|
||||
high = tm->tm_min / 10;
|
||||
low = tm->tm_min;
|
||||
low = low - high * 10;
|
||||
buf[RTC_MIN] = (high << 4) + low;
|
||||
high = tm->tm_sec / 10;
|
||||
low = tm->tm_sec;
|
||||
low = low - high * 10;
|
||||
buf[RTC_SEC] = (high << 4) + low;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max8925_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct max8925_rtc_info *info = dev_get_drvdata(dev);
|
||||
unsigned char buf[TIME_NUM];
|
||||
int ret;
|
||||
|
||||
ret = max8925_bulk_read(info->rtc, MAX8925_RTC_SEC, TIME_NUM, buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = tm_calc(tm, buf, TIME_NUM);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8925_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct max8925_rtc_info *info = dev_get_drvdata(dev);
|
||||
unsigned char buf[TIME_NUM];
|
||||
int ret;
|
||||
|
||||
ret = data_calc(buf, tm, TIME_NUM);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = max8925_bulk_write(info->rtc, MAX8925_RTC_SEC, TIME_NUM, buf);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8925_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct max8925_rtc_info *info = dev_get_drvdata(dev);
|
||||
unsigned char buf[TIME_NUM];
|
||||
int ret;
|
||||
|
||||
ret = max8925_bulk_read(info->rtc, MAX8925_ALARM0_SEC, TIME_NUM, buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = tm_calc(&alrm->time, buf, TIME_NUM);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = max8925_reg_read(info->rtc, MAX8925_RTC_IRQ_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if ((ret & ALARM0_IRQ) == 0)
|
||||
alrm->enabled = 1;
|
||||
else
|
||||
alrm->enabled = 0;
|
||||
ret = max8925_reg_read(info->rtc, MAX8925_RTC_STATUS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if (ret & ALARM0_STATUS)
|
||||
alrm->pending = 1;
|
||||
else
|
||||
alrm->pending = 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8925_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct max8925_rtc_info *info = dev_get_drvdata(dev);
|
||||
unsigned char buf[TIME_NUM];
|
||||
int ret;
|
||||
|
||||
ret = data_calc(buf, &alrm->time, TIME_NUM);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = max8925_bulk_write(info->rtc, MAX8925_ALARM0_SEC, TIME_NUM, buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
/* only enable alarm on year/month/day/hour/min/sec */
|
||||
ret = max8925_reg_write(info->rtc, MAX8925_ALARM0_CNTL, 0x77);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops max8925_rtc_ops = {
|
||||
.read_time = max8925_rtc_read_time,
|
||||
.set_time = max8925_rtc_set_time,
|
||||
.read_alarm = max8925_rtc_read_alarm,
|
||||
.set_alarm = max8925_rtc_set_alarm,
|
||||
};
|
||||
|
||||
static int __devinit max8925_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct max8925_rtc_info *info;
|
||||
int irq, ret;
|
||||
|
||||
info = kzalloc(sizeof(struct max8925_rtc_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
info->chip = chip;
|
||||
info->rtc = chip->rtc;
|
||||
info->dev = &pdev->dev;
|
||||
irq = chip->irq_base + MAX8925_IRQ_RTC_ALARM0;
|
||||
|
||||
ret = request_threaded_irq(irq, NULL, rtc_update_handler,
|
||||
IRQF_ONESHOT, "rtc-alarm0", info);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
|
||||
irq, ret);
|
||||
goto out_irq;
|
||||
}
|
||||
|
||||
info->rtc_dev = rtc_device_register("max8925-rtc", &pdev->dev,
|
||||
&max8925_rtc_ops, THIS_MODULE);
|
||||
ret = PTR_ERR(info->rtc_dev);
|
||||
if (IS_ERR(info->rtc_dev)) {
|
||||
dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
|
||||
goto out_rtc;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, info);
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
return 0;
|
||||
out_rtc:
|
||||
free_irq(chip->irq_base + MAX8925_IRQ_RTC_ALARM0, info);
|
||||
out_irq:
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit max8925_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_rtc_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
if (info) {
|
||||
free_irq(info->chip->irq_base + MAX8925_IRQ_RTC_ALARM0, info);
|
||||
rtc_device_unregister(info->rtc_dev);
|
||||
kfree(info);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max8925_rtc_driver = {
|
||||
.driver = {
|
||||
.name = "max8925-rtc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max8925_rtc_probe,
|
||||
.remove = __devexit_p(max8925_rtc_remove),
|
||||
};
|
||||
|
||||
static int __init max8925_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&max8925_rtc_driver);
|
||||
}
|
||||
module_init(max8925_rtc_init);
|
||||
|
||||
static void __exit max8925_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&max8925_rtc_driver);
|
||||
}
|
||||
module_exit(max8925_rtc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Maxim MAX8925 RTC driver");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -307,11 +307,18 @@ static int wm8350_rtc_update_irq_enable(struct device *dev,
|
|||
{
|
||||
struct wm8350 *wm8350 = dev_get_drvdata(dev);
|
||||
|
||||
/* Suppress duplicate changes since genirq nests enable and
|
||||
* disable calls. */
|
||||
if (enabled == wm8350->rtc.update_enabled)
|
||||
return 0;
|
||||
|
||||
if (enabled)
|
||||
wm8350_unmask_irq(wm8350, WM8350_IRQ_RTC_SEC);
|
||||
else
|
||||
wm8350_mask_irq(wm8350, WM8350_IRQ_RTC_SEC);
|
||||
|
||||
wm8350->rtc.update_enabled = enabled;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -478,8 +485,8 @@ static int __devexit wm8350_rtc_remove(struct platform_device *pdev)
|
|||
struct wm8350 *wm8350 = platform_get_drvdata(pdev);
|
||||
struct wm8350_rtc *wm_rtc = &wm8350->rtc;
|
||||
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_RTC_ALM);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC, wm8350);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_RTC_ALM, wm8350);
|
||||
|
||||
rtc_device_unregister(wm_rtc->rtc);
|
||||
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Backlight driver for Marvell Semiconductor 88PM8606
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/mfd/88pm860x.h>
|
||||
|
||||
#define MAX_BRIGHTNESS (0xFF)
|
||||
#define MIN_BRIGHTNESS (0)
|
||||
|
||||
#define CURRENT_MASK (0x1F << 1)
|
||||
|
||||
struct pm860x_backlight_data {
|
||||
struct pm860x_chip *chip;
|
||||
struct i2c_client *i2c;
|
||||
int current_brightness;
|
||||
int port;
|
||||
int pwm;
|
||||
int iset;
|
||||
};
|
||||
|
||||
static inline int wled_a(int port)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ((port - PM8606_BACKLIGHT1) << 1) + 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int wled_b(int port)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ((port - PM8606_BACKLIGHT1) << 1) + 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* WLED2 & WLED3 share the same IDC */
|
||||
static inline int wled_idc(int port)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (port) {
|
||||
case PM8606_BACKLIGHT1:
|
||||
case PM8606_BACKLIGHT2:
|
||||
ret = ((port - PM8606_BACKLIGHT1) << 1) + 3;
|
||||
break;
|
||||
case PM8606_BACKLIGHT3:
|
||||
default:
|
||||
ret = ((port - PM8606_BACKLIGHT2) << 1) + 3;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_backlight_set(struct backlight_device *bl, int brightness)
|
||||
{
|
||||
struct pm860x_backlight_data *data = bl_get_data(bl);
|
||||
struct pm860x_chip *chip = data->chip;
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
if (brightness > MAX_BRIGHTNESS)
|
||||
value = MAX_BRIGHTNESS;
|
||||
else
|
||||
value = brightness;
|
||||
|
||||
ret = pm860x_reg_write(data->i2c, wled_a(data->port), value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if ((data->current_brightness == 0) && brightness) {
|
||||
if (data->iset) {
|
||||
ret = pm860x_set_bits(data->i2c, wled_idc(data->port),
|
||||
CURRENT_MASK, data->iset);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
if (data->pwm) {
|
||||
ret = pm860x_set_bits(data->i2c, PM8606_PWM,
|
||||
PM8606_PWM_FREQ_MASK, data->pwm);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
if (brightness == MAX_BRIGHTNESS) {
|
||||
/* set WLED_ON bit as 100% */
|
||||
ret = pm860x_set_bits(data->i2c, wled_b(data->port),
|
||||
PM8606_WLED_ON, PM8606_WLED_ON);
|
||||
}
|
||||
} else {
|
||||
if (brightness == MAX_BRIGHTNESS) {
|
||||
/* set WLED_ON bit as 100% */
|
||||
ret = pm860x_set_bits(data->i2c, wled_b(data->port),
|
||||
PM8606_WLED_ON, PM8606_WLED_ON);
|
||||
} else {
|
||||
/* clear WLED_ON bit since it's not 100% */
|
||||
ret = pm860x_set_bits(data->i2c, wled_b(data->port),
|
||||
PM8606_WLED_ON, 0);
|
||||
}
|
||||
}
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
dev_dbg(chip->dev, "set brightness %d\n", value);
|
||||
data->current_brightness = value;
|
||||
return 0;
|
||||
out:
|
||||
dev_dbg(chip->dev, "set brightness %d failure with return "
|
||||
"value:%d\n", value, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_backlight_update_status(struct backlight_device *bl)
|
||||
{
|
||||
int brightness = bl->props.brightness;
|
||||
|
||||
if (bl->props.power != FB_BLANK_UNBLANK)
|
||||
brightness = 0;
|
||||
|
||||
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
|
||||
brightness = 0;
|
||||
|
||||
if (bl->props.state & BL_CORE_SUSPENDED)
|
||||
brightness = 0;
|
||||
|
||||
return pm860x_backlight_set(bl, brightness);
|
||||
}
|
||||
|
||||
static int pm860x_backlight_get_brightness(struct backlight_device *bl)
|
||||
{
|
||||
struct pm860x_backlight_data *data = bl_get_data(bl);
|
||||
struct pm860x_chip *chip = data->chip;
|
||||
int ret;
|
||||
|
||||
ret = pm860x_reg_read(data->i2c, wled_a(data->port));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->current_brightness = ret;
|
||||
dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
|
||||
return data->current_brightness;
|
||||
out:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct backlight_ops pm860x_backlight_ops = {
|
||||
.options = BL_CORE_SUSPENDRESUME,
|
||||
.update_status = pm860x_backlight_update_status,
|
||||
.get_brightness = pm860x_backlight_get_brightness,
|
||||
};
|
||||
|
||||
static int __check_device(struct pm860x_backlight_pdata *pdata, char *name)
|
||||
{
|
||||
struct pm860x_backlight_pdata *p = pdata;
|
||||
int ret = -EINVAL;
|
||||
|
||||
while (p && p->id) {
|
||||
if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0))
|
||||
break;
|
||||
|
||||
if (!strncmp(name, pm860x_backlight_name[p->flags],
|
||||
MFD_NAME_SIZE)) {
|
||||
ret = (int)p->flags;
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_backlight_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct pm860x_platform_data *pm860x_pdata;
|
||||
struct pm860x_backlight_pdata *pdata = NULL;
|
||||
struct pm860x_backlight_data *data;
|
||||
struct backlight_device *bl;
|
||||
struct resource *res;
|
||||
unsigned char value;
|
||||
char name[MFD_NAME_SIZE];
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "No I/O resource!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pdev->dev.parent->platform_data) {
|
||||
pm860x_pdata = pdev->dev.parent->platform_data;
|
||||
pdata = pm860x_pdata->backlight;
|
||||
}
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "platform data isn't assigned to "
|
||||
"backlight\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
strncpy(name, res->name, MFD_NAME_SIZE);
|
||||
data->chip = chip;
|
||||
data->i2c = (chip->id == CHIP_PM8606) ? chip->client \
|
||||
: chip->companion;
|
||||
data->current_brightness = MAX_BRIGHTNESS;
|
||||
data->pwm = pdata->pwm;
|
||||
data->iset = pdata->iset;
|
||||
data->port = __check_device(pdata, name);
|
||||
if (data->port < 0) {
|
||||
dev_err(&pdev->dev, "wrong platform data is assigned");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bl = backlight_device_register(name, &pdev->dev, data,
|
||||
&pm860x_backlight_ops);
|
||||
if (IS_ERR(bl)) {
|
||||
dev_err(&pdev->dev, "failed to register backlight\n");
|
||||
kfree(data);
|
||||
return PTR_ERR(bl);
|
||||
}
|
||||
bl->props.max_brightness = MAX_BRIGHTNESS;
|
||||
bl->props.brightness = MAX_BRIGHTNESS;
|
||||
|
||||
platform_set_drvdata(pdev, bl);
|
||||
|
||||
/* Enable reference VSYS */
|
||||
ret = pm860x_reg_read(data->i2c, PM8606_VSYS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if ((ret & PM8606_VSYS_EN) == 0) {
|
||||
value = ret | PM8606_VSYS_EN;
|
||||
ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
/* Enable reference OSC */
|
||||
ret = pm860x_reg_read(data->i2c, PM8606_MISC);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if ((ret & PM8606_MISC_OSC_EN) == 0) {
|
||||
value = ret | PM8606_MISC_OSC_EN;
|
||||
ret = pm860x_reg_write(data->i2c, PM8606_MISC, value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
/* read current backlight */
|
||||
ret = pm860x_backlight_get_brightness(bl);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
backlight_update_status(bl);
|
||||
return 0;
|
||||
out:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pm860x_backlight_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct backlight_device *bl = platform_get_drvdata(pdev);
|
||||
struct pm860x_backlight_data *data = bl_get_data(bl);
|
||||
|
||||
backlight_device_unregister(bl);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pm860x_backlight_driver = {
|
||||
.driver = {
|
||||
.name = "88pm860x-backlight",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_backlight_probe,
|
||||
.remove = pm860x_backlight_remove,
|
||||
};
|
||||
|
||||
static int __init pm860x_backlight_init(void)
|
||||
{
|
||||
return platform_driver_register(&pm860x_backlight_driver);
|
||||
}
|
||||
module_init(pm860x_backlight_init);
|
||||
|
||||
static void __exit pm860x_backlight_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pm860x_backlight_driver);
|
||||
}
|
||||
module_exit(pm860x_backlight_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:88pm860x-backlight");
|
|
@ -212,6 +212,13 @@ config BACKLIGHT_DA903X
|
|||
If you have a LCD backlight connected to the WLED output of DA9030
|
||||
or DA9034 WLED output, say Y here to enable this driver.
|
||||
|
||||
config BACKLIGHT_MAX8925
|
||||
tristate "Backlight driver for MAX8925"
|
||||
depends on BACKLIGHT_CLASS_DEVICE && MFD_MAX8925
|
||||
help
|
||||
If you have a LCD backlight connected to the WLED output of MAX8925
|
||||
WLED output, say Y here to enable this driver.
|
||||
|
||||
config BACKLIGHT_MBP_NVIDIA
|
||||
tristate "MacBook Pro Nvidia Backlight Driver"
|
||||
depends on BACKLIGHT_CLASS_DEVICE && X86
|
||||
|
@ -262,3 +269,9 @@ config BACKLIGHT_ADP5520
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called adp5520_bl.
|
||||
|
||||
config BACKLIGHT_88PM860X
|
||||
tristate "Backlight Driver for 88PM8606 using WLED"
|
||||
depends on BACKLIGHT_CLASS_DEVICE && MFD_88PM860X
|
||||
help
|
||||
Say Y to enable the backlight driver for Marvell 88PM8606.
|
||||
|
||||
|
|
|
@ -22,10 +22,12 @@ obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o
|
|||
obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
|
||||
obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_MBP_NVIDIA) += mbp_nvidia_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_ADX) += adx_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o
|
||||
obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o
|
||||
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Backlight driver for Maxim MAX8925
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/mfd/max8925.h>
|
||||
|
||||
#define MAX_BRIGHTNESS (0xff)
|
||||
#define MIN_BRIGHTNESS (0)
|
||||
|
||||
#define LWX_FREQ(x) (((x - 601) / 100) & 0x7)
|
||||
|
||||
struct max8925_backlight_data {
|
||||
struct max8925_chip *chip;
|
||||
|
||||
int current_brightness;
|
||||
};
|
||||
|
||||
static int max8925_backlight_set(struct backlight_device *bl, int brightness)
|
||||
{
|
||||
struct max8925_backlight_data *data = bl_get_data(bl);
|
||||
struct max8925_chip *chip = data->chip;
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
if (brightness > MAX_BRIGHTNESS)
|
||||
value = MAX_BRIGHTNESS;
|
||||
else
|
||||
value = brightness;
|
||||
|
||||
ret = max8925_reg_write(chip->i2c, MAX8925_WLED_CNTL, value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (!data->current_brightness && brightness)
|
||||
/* enable WLED output */
|
||||
ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 1, 1);
|
||||
else if (!brightness)
|
||||
/* disable WLED output */
|
||||
ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 1, 0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
dev_dbg(chip->dev, "set brightness %d\n", value);
|
||||
data->current_brightness = value;
|
||||
return 0;
|
||||
out:
|
||||
dev_dbg(chip->dev, "set brightness %d failure with return value:%d\n",
|
||||
value, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8925_backlight_update_status(struct backlight_device *bl)
|
||||
{
|
||||
int brightness = bl->props.brightness;
|
||||
|
||||
if (bl->props.power != FB_BLANK_UNBLANK)
|
||||
brightness = 0;
|
||||
|
||||
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
|
||||
brightness = 0;
|
||||
|
||||
if (bl->props.state & BL_CORE_SUSPENDED)
|
||||
brightness = 0;
|
||||
|
||||
return max8925_backlight_set(bl, brightness);
|
||||
}
|
||||
|
||||
static int max8925_backlight_get_brightness(struct backlight_device *bl)
|
||||
{
|
||||
struct max8925_backlight_data *data = bl_get_data(bl);
|
||||
struct max8925_chip *chip = data->chip;
|
||||
int ret;
|
||||
|
||||
ret = max8925_reg_read(chip->i2c, MAX8925_WLED_CNTL);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
data->current_brightness = ret;
|
||||
dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct backlight_ops max8925_backlight_ops = {
|
||||
.options = BL_CORE_SUSPENDRESUME,
|
||||
.update_status = max8925_backlight_update_status,
|
||||
.get_brightness = max8925_backlight_get_brightness,
|
||||
};
|
||||
|
||||
static int __devinit max8925_backlight_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
|
||||
struct max8925_platform_data *max8925_pdata;
|
||||
struct max8925_backlight_pdata *pdata = NULL;
|
||||
struct max8925_backlight_data *data;
|
||||
struct backlight_device *bl;
|
||||
struct resource *res;
|
||||
char name[MAX8925_NAME_SIZE];
|
||||
unsigned char value;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "No I/O resource!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pdev->dev.parent->platform_data) {
|
||||
max8925_pdata = pdev->dev.parent->platform_data;
|
||||
pdata = max8925_pdata->backlight;
|
||||
}
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform data isn't assigned to "
|
||||
"backlight\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct max8925_backlight_data), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
strncpy(name, res->name, MAX8925_NAME_SIZE);
|
||||
data->chip = chip;
|
||||
data->current_brightness = 0;
|
||||
|
||||
bl = backlight_device_register(name, &pdev->dev, data,
|
||||
&max8925_backlight_ops);
|
||||
if (IS_ERR(bl)) {
|
||||
dev_err(&pdev->dev, "failed to register backlight\n");
|
||||
kfree(data);
|
||||
return PTR_ERR(bl);
|
||||
}
|
||||
bl->props.max_brightness = MAX_BRIGHTNESS;
|
||||
bl->props.brightness = MAX_BRIGHTNESS;
|
||||
|
||||
platform_set_drvdata(pdev, bl);
|
||||
|
||||
value = 0;
|
||||
if (pdata->lxw_scl)
|
||||
value |= (1 << 7);
|
||||
if (pdata->lxw_freq)
|
||||
value |= (LWX_FREQ(pdata->lxw_freq) << 4);
|
||||
if (pdata->dual_string)
|
||||
value |= (1 << 1);
|
||||
ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 0xfe, value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
backlight_update_status(bl);
|
||||
return 0;
|
||||
out:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit max8925_backlight_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct backlight_device *bl = platform_get_drvdata(pdev);
|
||||
struct max8925_backlight_data *data = bl_get_data(bl);
|
||||
|
||||
backlight_device_unregister(bl);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max8925_backlight_driver = {
|
||||
.driver = {
|
||||
.name = "max8925-backlight",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max8925_backlight_probe,
|
||||
.remove = __devexit_p(max8925_backlight_remove),
|
||||
};
|
||||
|
||||
static int __init max8925_backlight_init(void)
|
||||
{
|
||||
return platform_driver_register(&max8925_backlight_driver);
|
||||
}
|
||||
module_init(max8925_backlight_init);
|
||||
|
||||
static void __exit max8925_backlight_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&max8925_backlight_driver);
|
||||
};
|
||||
module_exit(max8925_backlight_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Backlight Driver for Maxim MAX8925");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:max8925-backlight");
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef __LINUX_HTCPLD_H
|
||||
#define __LINUX_HTCPLD_H
|
||||
|
||||
struct htcpld_chip_platform_data {
|
||||
unsigned int addr;
|
||||
unsigned int reset;
|
||||
unsigned int num_gpios;
|
||||
unsigned int gpio_out_base;
|
||||
unsigned int gpio_in_base;
|
||||
unsigned int irq_base;
|
||||
unsigned int num_irqs;
|
||||
};
|
||||
|
||||
struct htcpld_core_platform_data {
|
||||
unsigned int int_reset_gpio_hi;
|
||||
unsigned int int_reset_gpio_lo;
|
||||
unsigned int i2c_adapter_id;
|
||||
|
||||
struct htcpld_chip_platform_data *chip;
|
||||
unsigned int num_chip;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_HTCPLD_H */
|
||||
|
|
@ -80,6 +80,11 @@
|
|||
#define TWL_MODULE_PM_MASTER TWL4030_MODULE_PM_MASTER
|
||||
#define TWL_MODULE_PM_RECEIVER TWL4030_MODULE_PM_RECEIVER
|
||||
#define TWL_MODULE_RTC TWL4030_MODULE_RTC
|
||||
#define TWL_MODULE_PWM TWL4030_MODULE_PWM0
|
||||
|
||||
#define TWL6030_MODULE_ID0 0x0D
|
||||
#define TWL6030_MODULE_ID1 0x0E
|
||||
#define TWL6030_MODULE_ID2 0x0F
|
||||
|
||||
#define GPIO_INTR_OFFSET 0
|
||||
#define KEYPAD_INTR_OFFSET 1
|
||||
|
@ -239,6 +244,21 @@ int twl6030_interrupt_mask(u8 bit_mask, u8 offset);
|
|||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*Interface Bit Register (INTBR) offsets
|
||||
*(Use TWL_4030_MODULE_INTBR)
|
||||
*/
|
||||
|
||||
#define REG_GPPUPDCTR1 0x0F
|
||||
|
||||
/*I2C1 and I2C4(SR) SDA/SCL pull-up control bits */
|
||||
|
||||
#define I2C_SCL_CTRL_PU BIT(0)
|
||||
#define I2C_SDA_CTRL_PU BIT(2)
|
||||
#define SR_I2C_SCL_CTRL_PU BIT(4)
|
||||
#define SR_I2C_SDA_CTRL_PU BIT(6)
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Keypad register offsets (use TWL4030_MODULE_KEYPAD)
|
||||
* ... SIH/interrupt only
|
||||
|
@ -530,6 +550,7 @@ struct twl4030_power_data {
|
|||
};
|
||||
|
||||
extern void twl4030_power_init(struct twl4030_power_data *triton2_scripts);
|
||||
extern int twl4030_remove_script(u8 flags);
|
||||
|
||||
struct twl4030_codec_audio_data {
|
||||
unsigned int audio_mclk;
|
||||
|
@ -605,12 +626,7 @@ int twl4030_sih_setup(int module);
|
|||
#define TWL4030_VAUX3_DEV_GRP 0x1F
|
||||
#define TWL4030_VAUX3_DEDICATED 0x22
|
||||
|
||||
#if defined(CONFIG_TWL4030_BCI_BATTERY) || \
|
||||
defined(CONFIG_TWL4030_BCI_BATTERY_MODULE)
|
||||
extern int twl4030charger_usb_en(int enable);
|
||||
#else
|
||||
static inline int twl4030charger_usb_en(int enable) { return 0; }
|
||||
#endif
|
||||
static inline int twl4030charger_usb_en(int enable) { return 0; }
|
||||
|
||||
/*----------------------------------------------------------------------*/
|
||||
|
||||
|
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
* Marvell 88PM8607 Interface
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_88PM8607_H
|
||||
#define __LINUX_MFD_88PM8607_H
|
||||
|
||||
enum {
|
||||
PM8607_ID_BUCK1 = 0,
|
||||
PM8607_ID_BUCK2,
|
||||
PM8607_ID_BUCK3,
|
||||
|
||||
PM8607_ID_LDO1,
|
||||
PM8607_ID_LDO2,
|
||||
PM8607_ID_LDO3,
|
||||
PM8607_ID_LDO4,
|
||||
PM8607_ID_LDO5,
|
||||
PM8607_ID_LDO6,
|
||||
PM8607_ID_LDO7,
|
||||
PM8607_ID_LDO8,
|
||||
PM8607_ID_LDO9,
|
||||
PM8607_ID_LDO10,
|
||||
PM8607_ID_LDO12,
|
||||
PM8607_ID_LDO14,
|
||||
|
||||
PM8607_ID_RG_MAX,
|
||||
};
|
||||
|
||||
#define CHIP_ID (0x40)
|
||||
#define CHIP_ID_MASK (0xF8)
|
||||
|
||||
/* Interrupt Registers */
|
||||
#define PM8607_STATUS_1 (0x01)
|
||||
#define PM8607_STATUS_2 (0x02)
|
||||
#define PM8607_INT_STATUS1 (0x03)
|
||||
#define PM8607_INT_STATUS2 (0x04)
|
||||
#define PM8607_INT_STATUS3 (0x05)
|
||||
#define PM8607_INT_MASK_1 (0x06)
|
||||
#define PM8607_INT_MASK_2 (0x07)
|
||||
#define PM8607_INT_MASK_3 (0x08)
|
||||
|
||||
/* Regulator Control Registers */
|
||||
#define PM8607_LDO1 (0x10)
|
||||
#define PM8607_LDO2 (0x11)
|
||||
#define PM8607_LDO3 (0x12)
|
||||
#define PM8607_LDO4 (0x13)
|
||||
#define PM8607_LDO5 (0x14)
|
||||
#define PM8607_LDO6 (0x15)
|
||||
#define PM8607_LDO7 (0x16)
|
||||
#define PM8607_LDO8 (0x17)
|
||||
#define PM8607_LDO9 (0x18)
|
||||
#define PM8607_LDO10 (0x19)
|
||||
#define PM8607_LDO12 (0x1A)
|
||||
#define PM8607_LDO14 (0x1B)
|
||||
#define PM8607_SLEEP_MODE1 (0x1C)
|
||||
#define PM8607_SLEEP_MODE2 (0x1D)
|
||||
#define PM8607_SLEEP_MODE3 (0x1E)
|
||||
#define PM8607_SLEEP_MODE4 (0x1F)
|
||||
#define PM8607_GO (0x20)
|
||||
#define PM8607_SLEEP_BUCK1 (0x21)
|
||||
#define PM8607_SLEEP_BUCK2 (0x22)
|
||||
#define PM8607_SLEEP_BUCK3 (0x23)
|
||||
#define PM8607_BUCK1 (0x24)
|
||||
#define PM8607_BUCK2 (0x25)
|
||||
#define PM8607_BUCK3 (0x26)
|
||||
#define PM8607_BUCK_CONTROLS (0x27)
|
||||
#define PM8607_SUPPLIES_EN11 (0x2B)
|
||||
#define PM8607_SUPPLIES_EN12 (0x2C)
|
||||
#define PM8607_GROUP1 (0x2D)
|
||||
#define PM8607_GROUP2 (0x2E)
|
||||
#define PM8607_GROUP3 (0x2F)
|
||||
#define PM8607_GROUP4 (0x30)
|
||||
#define PM8607_GROUP5 (0x31)
|
||||
#define PM8607_GROUP6 (0x32)
|
||||
#define PM8607_SUPPLIES_EN21 (0x33)
|
||||
#define PM8607_SUPPLIES_EN22 (0x34)
|
||||
|
||||
/* RTC Control Registers */
|
||||
#define PM8607_RTC1 (0xA0)
|
||||
#define PM8607_RTC_COUNTER1 (0xA1)
|
||||
#define PM8607_RTC_COUNTER2 (0xA2)
|
||||
#define PM8607_RTC_COUNTER3 (0xA3)
|
||||
#define PM8607_RTC_COUNTER4 (0xA4)
|
||||
#define PM8607_RTC_EXPIRE1 (0xA5)
|
||||
#define PM8607_RTC_EXPIRE2 (0xA6)
|
||||
#define PM8607_RTC_EXPIRE3 (0xA7)
|
||||
#define PM8607_RTC_EXPIRE4 (0xA8)
|
||||
#define PM8607_RTC_TRIM1 (0xA9)
|
||||
#define PM8607_RTC_TRIM2 (0xAA)
|
||||
#define PM8607_RTC_TRIM3 (0xAB)
|
||||
#define PM8607_RTC_TRIM4 (0xAC)
|
||||
#define PM8607_RTC_MISC1 (0xAD)
|
||||
#define PM8607_RTC_MISC2 (0xAE)
|
||||
#define PM8607_RTC_MISC3 (0xAF)
|
||||
|
||||
/* Misc Registers */
|
||||
#define PM8607_CHIP_ID (0x00)
|
||||
#define PM8607_LDO1 (0x10)
|
||||
#define PM8607_DVC3 (0x26)
|
||||
#define PM8607_MISC1 (0x40)
|
||||
|
||||
/* bit definitions for PM8607 events */
|
||||
#define PM8607_EVENT_ONKEY (1 << 0)
|
||||
#define PM8607_EVENT_EXTON (1 << 1)
|
||||
#define PM8607_EVENT_CHG (1 << 2)
|
||||
#define PM8607_EVENT_BAT (1 << 3)
|
||||
#define PM8607_EVENT_RTC (1 << 4)
|
||||
#define PM8607_EVENT_CC (1 << 5)
|
||||
#define PM8607_EVENT_VBAT (1 << 8)
|
||||
#define PM8607_EVENT_VCHG (1 << 9)
|
||||
#define PM8607_EVENT_VSYS (1 << 10)
|
||||
#define PM8607_EVENT_TINT (1 << 11)
|
||||
#define PM8607_EVENT_GPADC0 (1 << 12)
|
||||
#define PM8607_EVENT_GPADC1 (1 << 13)
|
||||
#define PM8607_EVENT_GPADC2 (1 << 14)
|
||||
#define PM8607_EVENT_GPADC3 (1 << 15)
|
||||
#define PM8607_EVENT_AUDIO_SHORT (1 << 16)
|
||||
#define PM8607_EVENT_PEN (1 << 17)
|
||||
#define PM8607_EVENT_HEADSET (1 << 18)
|
||||
#define PM8607_EVENT_HOOK (1 << 19)
|
||||
#define PM8607_EVENT_MICIN (1 << 20)
|
||||
#define PM8607_EVENT_CHG_TIMEOUT (1 << 21)
|
||||
#define PM8607_EVENT_CHG_DONE (1 << 22)
|
||||
#define PM8607_EVENT_CHG_FAULT (1 << 23)
|
||||
|
||||
/* bit definitions of Status Query Interface */
|
||||
#define PM8607_STATUS_CC (1 << 3)
|
||||
#define PM8607_STATUS_PEN (1 << 4)
|
||||
#define PM8607_STATUS_HEADSET (1 << 5)
|
||||
#define PM8607_STATUS_HOOK (1 << 6)
|
||||
#define PM8607_STATUS_MICIN (1 << 7)
|
||||
#define PM8607_STATUS_ONKEY (1 << 8)
|
||||
#define PM8607_STATUS_EXTON (1 << 9)
|
||||
#define PM8607_STATUS_CHG (1 << 10)
|
||||
#define PM8607_STATUS_BAT (1 << 11)
|
||||
#define PM8607_STATUS_VBUS (1 << 12)
|
||||
#define PM8607_STATUS_OV (1 << 13)
|
||||
|
||||
/* bit definitions of BUCK3 */
|
||||
#define PM8607_BUCK3_DOUBLE (1 << 6)
|
||||
|
||||
/* bit definitions of Misc1 */
|
||||
#define PM8607_MISC1_PI2C (1 << 0)
|
||||
|
||||
/* Interrupt Number in 88PM8607 */
|
||||
enum {
|
||||
PM8607_IRQ_ONKEY = 0,
|
||||
PM8607_IRQ_EXTON,
|
||||
PM8607_IRQ_CHG,
|
||||
PM8607_IRQ_BAT,
|
||||
PM8607_IRQ_RTC,
|
||||
PM8607_IRQ_VBAT = 8,
|
||||
PM8607_IRQ_VCHG,
|
||||
PM8607_IRQ_VSYS,
|
||||
PM8607_IRQ_TINT,
|
||||
PM8607_IRQ_GPADC0,
|
||||
PM8607_IRQ_GPADC1,
|
||||
PM8607_IRQ_GPADC2,
|
||||
PM8607_IRQ_GPADC3,
|
||||
PM8607_IRQ_AUDIO_SHORT = 16,
|
||||
PM8607_IRQ_PEN,
|
||||
PM8607_IRQ_HEADSET,
|
||||
PM8607_IRQ_HOOK,
|
||||
PM8607_IRQ_MICIN,
|
||||
PM8607_IRQ_CHG_FAIL,
|
||||
PM8607_IRQ_CHG_DONE,
|
||||
PM8607_IRQ_CHG_FAULT,
|
||||
};
|
||||
|
||||
enum {
|
||||
PM8607_CHIP_A0 = 0x40,
|
||||
PM8607_CHIP_A1 = 0x41,
|
||||
PM8607_CHIP_B0 = 0x48,
|
||||
};
|
||||
|
||||
|
||||
struct pm8607_chip {
|
||||
struct device *dev;
|
||||
struct mutex io_lock;
|
||||
struct i2c_client *client;
|
||||
|
||||
int (*read)(struct pm8607_chip *chip, int reg, int bytes, void *dest);
|
||||
int (*write)(struct pm8607_chip *chip, int reg, int bytes, void *src);
|
||||
|
||||
int buck3_double; /* DVC ramp slope double */
|
||||
unsigned char chip_id;
|
||||
|
||||
};
|
||||
|
||||
#define PM8607_MAX_REGULATOR 15 /* 3 Bucks, 12 LDOs */
|
||||
|
||||
enum {
|
||||
GI2C_PORT = 0,
|
||||
PI2C_PORT,
|
||||
};
|
||||
|
||||
struct pm8607_platform_data {
|
||||
int i2c_port; /* Controlled by GI2C or PI2C */
|
||||
struct regulator_init_data *regulator[PM8607_MAX_REGULATOR];
|
||||
};
|
||||
|
||||
extern int pm8607_reg_read(struct pm8607_chip *, int);
|
||||
extern int pm8607_reg_write(struct pm8607_chip *, int, unsigned char);
|
||||
extern int pm8607_bulk_read(struct pm8607_chip *, int, int,
|
||||
unsigned char *);
|
||||
extern int pm8607_bulk_write(struct pm8607_chip *, int, int,
|
||||
unsigned char *);
|
||||
extern int pm8607_set_bits(struct pm8607_chip *, int, unsigned char,
|
||||
unsigned char);
|
||||
#endif /* __LINUX_MFD_88PM8607_H */
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* Marvell 88PM860x Interface
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_88PM860X_H
|
||||
#define __LINUX_MFD_88PM860X_H
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#define MFD_NAME_SIZE (40)
|
||||
|
||||
enum {
|
||||
CHIP_INVALID = 0,
|
||||
CHIP_PM8606,
|
||||
CHIP_PM8607,
|
||||
CHIP_MAX,
|
||||
};
|
||||
|
||||
enum {
|
||||
PM8606_ID_INVALID,
|
||||
PM8606_ID_BACKLIGHT,
|
||||
PM8606_ID_LED,
|
||||
PM8606_ID_VIBRATOR,
|
||||
PM8606_ID_TOUCH,
|
||||
PM8606_ID_SOUND,
|
||||
PM8606_ID_CHARGER,
|
||||
PM8606_ID_MAX,
|
||||
};
|
||||
|
||||
enum {
|
||||
PM8606_BACKLIGHT1 = 0,
|
||||
PM8606_BACKLIGHT2,
|
||||
PM8606_BACKLIGHT3,
|
||||
};
|
||||
|
||||
enum {
|
||||
PM8606_LED1_RED = 0,
|
||||
PM8606_LED1_GREEN,
|
||||
PM8606_LED1_BLUE,
|
||||
PM8606_LED2_RED,
|
||||
PM8606_LED2_GREEN,
|
||||
PM8606_LED2_BLUE,
|
||||
PM8607_LED_VIBRATOR,
|
||||
};
|
||||
|
||||
|
||||
/* 8606 Registers */
|
||||
#define PM8606_DCM_BOOST (0x00)
|
||||
#define PM8606_PWM (0x01)
|
||||
|
||||
/* Backlight Registers */
|
||||
#define PM8606_WLED1A (0x02)
|
||||
#define PM8606_WLED1B (0x03)
|
||||
#define PM8606_WLED2A (0x04)
|
||||
#define PM8606_WLED2B (0x05)
|
||||
#define PM8606_WLED3A (0x06)
|
||||
#define PM8606_WLED3B (0x07)
|
||||
|
||||
/* LED Registers */
|
||||
#define PM8606_RGB2A (0x08)
|
||||
#define PM8606_RGB2B (0x09)
|
||||
#define PM8606_RGB2C (0x0A)
|
||||
#define PM8606_RGB2D (0x0B)
|
||||
#define PM8606_RGB1A (0x0C)
|
||||
#define PM8606_RGB1B (0x0D)
|
||||
#define PM8606_RGB1C (0x0E)
|
||||
#define PM8606_RGB1D (0x0F)
|
||||
|
||||
#define PM8606_PREREGULATORA (0x10)
|
||||
#define PM8606_PREREGULATORB (0x11)
|
||||
#define PM8606_VIBRATORA (0x12)
|
||||
#define PM8606_VIBRATORB (0x13)
|
||||
#define PM8606_VCHG (0x14)
|
||||
#define PM8606_VSYS (0x15)
|
||||
#define PM8606_MISC (0x16)
|
||||
#define PM8606_CHIP_ID (0x17)
|
||||
#define PM8606_STATUS (0x18)
|
||||
#define PM8606_FLAGS (0x19)
|
||||
#define PM8606_PROTECTA (0x1A)
|
||||
#define PM8606_PROTECTB (0x1B)
|
||||
#define PM8606_PROTECTC (0x1C)
|
||||
|
||||
/* Bit definitions of PM8606 registers */
|
||||
#define PM8606_DCM_500MA (0x0) /* current limit */
|
||||
#define PM8606_DCM_750MA (0x1)
|
||||
#define PM8606_DCM_1000MA (0x2)
|
||||
#define PM8606_DCM_1250MA (0x3)
|
||||
#define PM8606_DCM_250MV (0x0 << 2)
|
||||
#define PM8606_DCM_300MV (0x1 << 2)
|
||||
#define PM8606_DCM_350MV (0x2 << 2)
|
||||
#define PM8606_DCM_400MV (0x3 << 2)
|
||||
|
||||
#define PM8606_PWM_31200HZ (0x0)
|
||||
#define PM8606_PWM_15600HZ (0x1)
|
||||
#define PM8606_PWM_7800HZ (0x2)
|
||||
#define PM8606_PWM_3900HZ (0x3)
|
||||
#define PM8606_PWM_1950HZ (0x4)
|
||||
#define PM8606_PWM_976HZ (0x5)
|
||||
#define PM8606_PWM_488HZ (0x6)
|
||||
#define PM8606_PWM_244HZ (0x7)
|
||||
#define PM8606_PWM_FREQ_MASK (0x7)
|
||||
|
||||
#define PM8606_WLED_ON (1 << 0)
|
||||
#define PM8606_WLED_CURRENT(x) ((x & 0x1F) << 1)
|
||||
|
||||
#define PM8606_LED_CURRENT(x) (((x >> 2) & 0x07) << 5)
|
||||
|
||||
#define PM8606_VSYS_EN (1 << 1)
|
||||
|
||||
#define PM8606_MISC_OSC_EN (1 << 4)
|
||||
|
||||
enum {
|
||||
PM8607_ID_BUCK1 = 0,
|
||||
PM8607_ID_BUCK2,
|
||||
PM8607_ID_BUCK3,
|
||||
|
||||
PM8607_ID_LDO1,
|
||||
PM8607_ID_LDO2,
|
||||
PM8607_ID_LDO3,
|
||||
PM8607_ID_LDO4,
|
||||
PM8607_ID_LDO5,
|
||||
PM8607_ID_LDO6,
|
||||
PM8607_ID_LDO7,
|
||||
PM8607_ID_LDO8,
|
||||
PM8607_ID_LDO9,
|
||||
PM8607_ID_LDO10,
|
||||
PM8607_ID_LDO12,
|
||||
PM8607_ID_LDO14,
|
||||
|
||||
PM8607_ID_RG_MAX,
|
||||
};
|
||||
|
||||
#define PM8607_VERSION (0x40) /* 8607 chip ID */
|
||||
#define PM8607_VERSION_MASK (0xF0) /* 8607 chip ID mask */
|
||||
|
||||
/* Interrupt Registers */
|
||||
#define PM8607_STATUS_1 (0x01)
|
||||
#define PM8607_STATUS_2 (0x02)
|
||||
#define PM8607_INT_STATUS1 (0x03)
|
||||
#define PM8607_INT_STATUS2 (0x04)
|
||||
#define PM8607_INT_STATUS3 (0x05)
|
||||
#define PM8607_INT_MASK_1 (0x06)
|
||||
#define PM8607_INT_MASK_2 (0x07)
|
||||
#define PM8607_INT_MASK_3 (0x08)
|
||||
|
||||
/* Regulator Control Registers */
|
||||
#define PM8607_LDO1 (0x10)
|
||||
#define PM8607_LDO2 (0x11)
|
||||
#define PM8607_LDO3 (0x12)
|
||||
#define PM8607_LDO4 (0x13)
|
||||
#define PM8607_LDO5 (0x14)
|
||||
#define PM8607_LDO6 (0x15)
|
||||
#define PM8607_LDO7 (0x16)
|
||||
#define PM8607_LDO8 (0x17)
|
||||
#define PM8607_LDO9 (0x18)
|
||||
#define PM8607_LDO10 (0x19)
|
||||
#define PM8607_LDO12 (0x1A)
|
||||
#define PM8607_LDO14 (0x1B)
|
||||
#define PM8607_SLEEP_MODE1 (0x1C)
|
||||
#define PM8607_SLEEP_MODE2 (0x1D)
|
||||
#define PM8607_SLEEP_MODE3 (0x1E)
|
||||
#define PM8607_SLEEP_MODE4 (0x1F)
|
||||
#define PM8607_GO (0x20)
|
||||
#define PM8607_SLEEP_BUCK1 (0x21)
|
||||
#define PM8607_SLEEP_BUCK2 (0x22)
|
||||
#define PM8607_SLEEP_BUCK3 (0x23)
|
||||
#define PM8607_BUCK1 (0x24)
|
||||
#define PM8607_BUCK2 (0x25)
|
||||
#define PM8607_BUCK3 (0x26)
|
||||
#define PM8607_BUCK_CONTROLS (0x27)
|
||||
#define PM8607_SUPPLIES_EN11 (0x2B)
|
||||
#define PM8607_SUPPLIES_EN12 (0x2C)
|
||||
#define PM8607_GROUP1 (0x2D)
|
||||
#define PM8607_GROUP2 (0x2E)
|
||||
#define PM8607_GROUP3 (0x2F)
|
||||
#define PM8607_GROUP4 (0x30)
|
||||
#define PM8607_GROUP5 (0x31)
|
||||
#define PM8607_GROUP6 (0x32)
|
||||
#define PM8607_SUPPLIES_EN21 (0x33)
|
||||
#define PM8607_SUPPLIES_EN22 (0x34)
|
||||
|
||||
/* Vibrator Control Registers */
|
||||
#define PM8607_VIBRATOR_SET (0x28)
|
||||
#define PM8607_VIBRATOR_PWM (0x29)
|
||||
|
||||
/* GPADC Registers */
|
||||
#define PM8607_GP_BIAS1 (0x4F)
|
||||
#define PM8607_MEAS_EN1 (0x50)
|
||||
#define PM8607_MEAS_EN2 (0x51)
|
||||
#define PM8607_MEAS_EN3 (0x52)
|
||||
#define PM8607_MEAS_OFF_TIME1 (0x53)
|
||||
#define PM8607_MEAS_OFF_TIME2 (0x54)
|
||||
#define PM8607_TSI_PREBIAS (0x55) /* prebias time */
|
||||
#define PM8607_PD_PREBIAS (0x56) /* prebias time */
|
||||
#define PM8607_GPADC_MISC1 (0x57)
|
||||
|
||||
/* RTC Control Registers */
|
||||
#define PM8607_RTC1 (0xA0)
|
||||
#define PM8607_RTC_COUNTER1 (0xA1)
|
||||
#define PM8607_RTC_COUNTER2 (0xA2)
|
||||
#define PM8607_RTC_COUNTER3 (0xA3)
|
||||
#define PM8607_RTC_COUNTER4 (0xA4)
|
||||
#define PM8607_RTC_EXPIRE1 (0xA5)
|
||||
#define PM8607_RTC_EXPIRE2 (0xA6)
|
||||
#define PM8607_RTC_EXPIRE3 (0xA7)
|
||||
#define PM8607_RTC_EXPIRE4 (0xA8)
|
||||
#define PM8607_RTC_TRIM1 (0xA9)
|
||||
#define PM8607_RTC_TRIM2 (0xAA)
|
||||
#define PM8607_RTC_TRIM3 (0xAB)
|
||||
#define PM8607_RTC_TRIM4 (0xAC)
|
||||
#define PM8607_RTC_MISC1 (0xAD)
|
||||
#define PM8607_RTC_MISC2 (0xAE)
|
||||
#define PM8607_RTC_MISC3 (0xAF)
|
||||
|
||||
/* Misc Registers */
|
||||
#define PM8607_CHIP_ID (0x00)
|
||||
#define PM8607_B0_MISC1 (0x0C)
|
||||
#define PM8607_LDO1 (0x10)
|
||||
#define PM8607_DVC3 (0x26)
|
||||
#define PM8607_A1_MISC1 (0x40)
|
||||
|
||||
/* bit definitions of Status Query Interface */
|
||||
#define PM8607_STATUS_CC (1 << 3)
|
||||
#define PM8607_STATUS_PEN (1 << 4)
|
||||
#define PM8607_STATUS_HEADSET (1 << 5)
|
||||
#define PM8607_STATUS_HOOK (1 << 6)
|
||||
#define PM8607_STATUS_MICIN (1 << 7)
|
||||
#define PM8607_STATUS_ONKEY (1 << 8)
|
||||
#define PM8607_STATUS_EXTON (1 << 9)
|
||||
#define PM8607_STATUS_CHG (1 << 10)
|
||||
#define PM8607_STATUS_BAT (1 << 11)
|
||||
#define PM8607_STATUS_VBUS (1 << 12)
|
||||
#define PM8607_STATUS_OV (1 << 13)
|
||||
|
||||
/* bit definitions of BUCK3 */
|
||||
#define PM8607_BUCK3_DOUBLE (1 << 6)
|
||||
|
||||
/* bit definitions of Misc1 */
|
||||
#define PM8607_A1_MISC1_PI2C (1 << 0)
|
||||
#define PM8607_B0_MISC1_INV_INT (1 << 0)
|
||||
#define PM8607_B0_MISC1_INT_CLEAR (1 << 1)
|
||||
#define PM8607_B0_MISC1_INT_MASK (1 << 2)
|
||||
#define PM8607_B0_MISC1_PI2C (1 << 3)
|
||||
#define PM8607_B0_MISC1_RESET (1 << 6)
|
||||
|
||||
/* bits definitions of GPADC */
|
||||
#define PM8607_GPADC_EN (1 << 0)
|
||||
#define PM8607_GPADC_PREBIAS_MASK (3 << 1)
|
||||
#define PM8607_GPADC_SLOT_CYCLE_MASK (3 << 3) /* slow mode */
|
||||
#define PM8607_GPADC_OFF_SCALE_MASK (3 << 5) /* GP sleep mode */
|
||||
#define PM8607_GPADC_SW_CAL_MASK (1 << 7)
|
||||
|
||||
#define PM8607_PD_PREBIAS_MASK (0x1F << 0)
|
||||
#define PM8607_PD_PRECHG_MASK (7 << 5)
|
||||
|
||||
/* Interrupt Number in 88PM8607 */
|
||||
enum {
|
||||
PM8607_IRQ_ONKEY,
|
||||
PM8607_IRQ_EXTON,
|
||||
PM8607_IRQ_CHG,
|
||||
PM8607_IRQ_BAT,
|
||||
PM8607_IRQ_RTC,
|
||||
PM8607_IRQ_CC,
|
||||
PM8607_IRQ_VBAT,
|
||||
PM8607_IRQ_VCHG,
|
||||
PM8607_IRQ_VSYS,
|
||||
PM8607_IRQ_TINT,
|
||||
PM8607_IRQ_GPADC0,
|
||||
PM8607_IRQ_GPADC1,
|
||||
PM8607_IRQ_GPADC2,
|
||||
PM8607_IRQ_GPADC3,
|
||||
PM8607_IRQ_AUDIO_SHORT,
|
||||
PM8607_IRQ_PEN,
|
||||
PM8607_IRQ_HEADSET,
|
||||
PM8607_IRQ_HOOK,
|
||||
PM8607_IRQ_MICIN,
|
||||
PM8607_IRQ_CHG_FAIL,
|
||||
PM8607_IRQ_CHG_DONE,
|
||||
PM8607_IRQ_CHG_FAULT,
|
||||
};
|
||||
|
||||
enum {
|
||||
PM8607_CHIP_A0 = 0x40,
|
||||
PM8607_CHIP_A1 = 0x41,
|
||||
PM8607_CHIP_B0 = 0x48,
|
||||
};
|
||||
|
||||
struct pm860x_chip {
|
||||
struct device *dev;
|
||||
struct mutex io_lock;
|
||||
struct mutex irq_lock;
|
||||
struct i2c_client *client;
|
||||
struct i2c_client *companion; /* companion chip client */
|
||||
|
||||
int buck3_double; /* DVC ramp slope double */
|
||||
unsigned short companion_addr;
|
||||
int id;
|
||||
int irq_mode;
|
||||
int irq_base;
|
||||
int core_irq;
|
||||
unsigned char chip_version;
|
||||
|
||||
};
|
||||
|
||||
#define PM8607_MAX_REGULATOR 15 /* 3 Bucks, 12 LDOs */
|
||||
|
||||
enum {
|
||||
GI2C_PORT = 0,
|
||||
PI2C_PORT,
|
||||
};
|
||||
|
||||
struct pm860x_backlight_pdata {
|
||||
int id;
|
||||
int pwm;
|
||||
int iset;
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
struct pm860x_led_pdata {
|
||||
int id;
|
||||
int iset;
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
struct pm860x_touch_pdata {
|
||||
int gpadc_prebias;
|
||||
int slot_cycle;
|
||||
int off_scale;
|
||||
int sw_cal;
|
||||
int tsi_prebias; /* time, slot */
|
||||
int pen_prebias; /* time, slot */
|
||||
int pen_prechg; /* time, slot */
|
||||
int res_x; /* resistor of Xplate */
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
struct pm860x_power_pdata {
|
||||
unsigned fast_charge; /* charge current */
|
||||
};
|
||||
|
||||
struct pm860x_platform_data {
|
||||
struct pm860x_backlight_pdata *backlight;
|
||||
struct pm860x_led_pdata *led;
|
||||
struct pm860x_touch_pdata *touch;
|
||||
struct pm860x_power_pdata *power;
|
||||
|
||||
unsigned short companion_addr; /* I2C address of companion chip */
|
||||
int i2c_port; /* Controlled by GI2C or PI2C */
|
||||
int irq_mode; /* Clear interrupt by read/write(0/1) */
|
||||
int irq_base; /* IRQ base number of 88pm860x */
|
||||
struct regulator_init_data *regulator[PM8607_MAX_REGULATOR];
|
||||
};
|
||||
|
||||
extern char pm860x_backlight_name[][MFD_NAME_SIZE];
|
||||
extern char pm860x_led_name[][MFD_NAME_SIZE];
|
||||
|
||||
extern int pm860x_reg_read(struct i2c_client *, int);
|
||||
extern int pm860x_reg_write(struct i2c_client *, int, unsigned char);
|
||||
extern int pm860x_bulk_read(struct i2c_client *, int, int, unsigned char *);
|
||||
extern int pm860x_bulk_write(struct i2c_client *, int, int, unsigned char *);
|
||||
extern int pm860x_set_bits(struct i2c_client *, int, unsigned char,
|
||||
unsigned char);
|
||||
|
||||
extern int pm860x_device_init(struct pm860x_chip *chip,
|
||||
struct pm860x_platform_data *pdata);
|
||||
extern void pm860x_device_exit(struct pm860x_chip *chip);
|
||||
|
||||
#endif /* __LINUX_MFD_88PM860X_H */
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
#ifndef MFD_AB3100_H
|
||||
|
@ -74,7 +73,6 @@
|
|||
* @testreg_client: secondary client for test registers
|
||||
* @chip_name: name of this chip variant
|
||||
* @chip_id: 8 bit chip ID for this chip variant
|
||||
* @work: an event handling worker
|
||||
* @event_subscribers: event subscribers are listed here
|
||||
* @startup_events: a copy of the first reading of the event registers
|
||||
* @startup_events_read: whether the first events have been read
|
||||
|
@ -90,7 +88,6 @@ struct ab3100 {
|
|||
struct i2c_client *testreg_client;
|
||||
char chip_name[32];
|
||||
u8 chip_id;
|
||||
struct work_struct work;
|
||||
struct blocking_notifier_head event_subscribers;
|
||||
u32 startup_events;
|
||||
bool startup_events_read;
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Maxim8925 Interface
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_MAX8925_H
|
||||
#define __LINUX_MFD_MAX8925_H
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
/* Unified sub device IDs for MAX8925 */
|
||||
enum {
|
||||
MAX8925_ID_SD1,
|
||||
MAX8925_ID_SD2,
|
||||
MAX8925_ID_SD3,
|
||||
MAX8925_ID_LDO1,
|
||||
MAX8925_ID_LDO2,
|
||||
MAX8925_ID_LDO3,
|
||||
MAX8925_ID_LDO4,
|
||||
MAX8925_ID_LDO5,
|
||||
MAX8925_ID_LDO6,
|
||||
MAX8925_ID_LDO7,
|
||||
MAX8925_ID_LDO8,
|
||||
MAX8925_ID_LDO9,
|
||||
MAX8925_ID_LDO10,
|
||||
MAX8925_ID_LDO11,
|
||||
MAX8925_ID_LDO12,
|
||||
MAX8925_ID_LDO13,
|
||||
MAX8925_ID_LDO14,
|
||||
MAX8925_ID_LDO15,
|
||||
MAX8925_ID_LDO16,
|
||||
MAX8925_ID_LDO17,
|
||||
MAX8925_ID_LDO18,
|
||||
MAX8925_ID_LDO19,
|
||||
MAX8925_ID_LDO20,
|
||||
MAX8925_ID_MAX,
|
||||
};
|
||||
|
||||
enum {
|
||||
/*
|
||||
* Charging current threshold trigger going from fast charge
|
||||
* to TOPOFF charge. From 5% to 20% of fasting charging current.
|
||||
*/
|
||||
MAX8925_TOPOFF_THR_5PER,
|
||||
MAX8925_TOPOFF_THR_10PER,
|
||||
MAX8925_TOPOFF_THR_15PER,
|
||||
MAX8925_TOPOFF_THR_20PER,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* Fast charging current */
|
||||
MAX8925_FCHG_85MA,
|
||||
MAX8925_FCHG_300MA,
|
||||
MAX8925_FCHG_460MA,
|
||||
MAX8925_FCHG_600MA,
|
||||
MAX8925_FCHG_700MA,
|
||||
MAX8925_FCHG_800MA,
|
||||
MAX8925_FCHG_900MA,
|
||||
MAX8925_FCHG_1000MA,
|
||||
};
|
||||
|
||||
/* Charger registers */
|
||||
#define MAX8925_CHG_IRQ1 (0x7e)
|
||||
#define MAX8925_CHG_IRQ2 (0x7f)
|
||||
#define MAX8925_CHG_IRQ1_MASK (0x80)
|
||||
#define MAX8925_CHG_IRQ2_MASK (0x81)
|
||||
#define MAX8925_CHG_STATUS (0x82)
|
||||
|
||||
/* GPM registers */
|
||||
#define MAX8925_SYSENSEL (0x00)
|
||||
#define MAX8925_ON_OFF_IRQ1 (0x01)
|
||||
#define MAX8925_ON_OFF_IRQ1_MASK (0x02)
|
||||
#define MAX8925_ON_OFF_STATUS (0x03)
|
||||
#define MAX8925_ON_OFF_IRQ2 (0x0d)
|
||||
#define MAX8925_ON_OFF_IRQ2_MASK (0x0e)
|
||||
#define MAX8925_RESET_CNFG (0x0f)
|
||||
|
||||
/* Touch registers */
|
||||
#define MAX8925_TSC_IRQ (0x00)
|
||||
#define MAX8925_TSC_IRQ_MASK (0x01)
|
||||
#define MAX8925_TSC_CNFG1 (0x02)
|
||||
#define MAX8925_ADC_SCHED (0x10)
|
||||
#define MAX8925_ADC_RES_END (0x6f)
|
||||
|
||||
#define MAX8925_NREF_OK (1 << 4)
|
||||
|
||||
/* RTC registers */
|
||||
#define MAX8925_ALARM0_CNTL (0x18)
|
||||
#define MAX8925_ALARM1_CNTL (0x19)
|
||||
#define MAX8925_RTC_IRQ (0x1c)
|
||||
#define MAX8925_RTC_IRQ_MASK (0x1d)
|
||||
#define MAX8925_MPL_CNTL (0x1e)
|
||||
|
||||
/* WLED registers */
|
||||
#define MAX8925_WLED_MODE_CNTL (0x84)
|
||||
#define MAX8925_WLED_CNTL (0x85)
|
||||
|
||||
/* MAX8925 Registers */
|
||||
#define MAX8925_SDCTL1 (0x04)
|
||||
#define MAX8925_SDCTL2 (0x07)
|
||||
#define MAX8925_SDCTL3 (0x0A)
|
||||
#define MAX8925_SDV1 (0x06)
|
||||
#define MAX8925_SDV2 (0x09)
|
||||
#define MAX8925_SDV3 (0x0C)
|
||||
#define MAX8925_LDOCTL1 (0x18)
|
||||
#define MAX8925_LDOCTL2 (0x1C)
|
||||
#define MAX8925_LDOCTL3 (0x20)
|
||||
#define MAX8925_LDOCTL4 (0x24)
|
||||
#define MAX8925_LDOCTL5 (0x28)
|
||||
#define MAX8925_LDOCTL6 (0x2C)
|
||||
#define MAX8925_LDOCTL7 (0x30)
|
||||
#define MAX8925_LDOCTL8 (0x34)
|
||||
#define MAX8925_LDOCTL9 (0x38)
|
||||
#define MAX8925_LDOCTL10 (0x3C)
|
||||
#define MAX8925_LDOCTL11 (0x40)
|
||||
#define MAX8925_LDOCTL12 (0x44)
|
||||
#define MAX8925_LDOCTL13 (0x48)
|
||||
#define MAX8925_LDOCTL14 (0x4C)
|
||||
#define MAX8925_LDOCTL15 (0x50)
|
||||
#define MAX8925_LDOCTL16 (0x10)
|
||||
#define MAX8925_LDOCTL17 (0x14)
|
||||
#define MAX8925_LDOCTL18 (0x72)
|
||||
#define MAX8925_LDOCTL19 (0x5C)
|
||||
#define MAX8925_LDOCTL20 (0x9C)
|
||||
#define MAX8925_LDOVOUT1 (0x1A)
|
||||
#define MAX8925_LDOVOUT2 (0x1E)
|
||||
#define MAX8925_LDOVOUT3 (0x22)
|
||||
#define MAX8925_LDOVOUT4 (0x26)
|
||||
#define MAX8925_LDOVOUT5 (0x2A)
|
||||
#define MAX8925_LDOVOUT6 (0x2E)
|
||||
#define MAX8925_LDOVOUT7 (0x32)
|
||||
#define MAX8925_LDOVOUT8 (0x36)
|
||||
#define MAX8925_LDOVOUT9 (0x3A)
|
||||
#define MAX8925_LDOVOUT10 (0x3E)
|
||||
#define MAX8925_LDOVOUT11 (0x42)
|
||||
#define MAX8925_LDOVOUT12 (0x46)
|
||||
#define MAX8925_LDOVOUT13 (0x4A)
|
||||
#define MAX8925_LDOVOUT14 (0x4E)
|
||||
#define MAX8925_LDOVOUT15 (0x52)
|
||||
#define MAX8925_LDOVOUT16 (0x12)
|
||||
#define MAX8925_LDOVOUT17 (0x16)
|
||||
#define MAX8925_LDOVOUT18 (0x74)
|
||||
#define MAX8925_LDOVOUT19 (0x5E)
|
||||
#define MAX8925_LDOVOUT20 (0x9E)
|
||||
|
||||
/* bit definitions */
|
||||
#define CHG_IRQ1_MASK (0x07)
|
||||
#define CHG_IRQ2_MASK (0xff)
|
||||
#define ON_OFF_IRQ1_MASK (0xff)
|
||||
#define ON_OFF_IRQ2_MASK (0x03)
|
||||
#define TSC_IRQ_MASK (0x03)
|
||||
#define RTC_IRQ_MASK (0x0c)
|
||||
|
||||
#define MAX8925_MAX_REGULATOR (23)
|
||||
|
||||
#define MAX8925_NAME_SIZE (32)
|
||||
|
||||
/* IRQ definitions */
|
||||
enum {
|
||||
MAX8925_IRQ_VCHG_DC_OVP,
|
||||
MAX8925_IRQ_VCHG_DC_F,
|
||||
MAX8925_IRQ_VCHG_DC_R,
|
||||
MAX8925_IRQ_VCHG_USB_OVP,
|
||||
MAX8925_IRQ_VCHG_USB_F,
|
||||
MAX8925_IRQ_VCHG_USB_R,
|
||||
MAX8925_IRQ_VCHG_THM_OK_R,
|
||||
MAX8925_IRQ_VCHG_THM_OK_F,
|
||||
MAX8925_IRQ_VCHG_SYSLOW_F,
|
||||
MAX8925_IRQ_VCHG_SYSLOW_R,
|
||||
MAX8925_IRQ_VCHG_RST,
|
||||
MAX8925_IRQ_VCHG_DONE,
|
||||
MAX8925_IRQ_VCHG_TOPOFF,
|
||||
MAX8925_IRQ_VCHG_TMR_FAULT,
|
||||
MAX8925_IRQ_GPM_RSTIN,
|
||||
MAX8925_IRQ_GPM_MPL,
|
||||
MAX8925_IRQ_GPM_SW_3SEC,
|
||||
MAX8925_IRQ_GPM_EXTON_F,
|
||||
MAX8925_IRQ_GPM_EXTON_R,
|
||||
MAX8925_IRQ_GPM_SW_1SEC,
|
||||
MAX8925_IRQ_GPM_SW_F,
|
||||
MAX8925_IRQ_GPM_SW_R,
|
||||
MAX8925_IRQ_GPM_SYSCKEN_F,
|
||||
MAX8925_IRQ_GPM_SYSCKEN_R,
|
||||
MAX8925_IRQ_RTC_ALARM1,
|
||||
MAX8925_IRQ_RTC_ALARM0,
|
||||
MAX8925_IRQ_TSC_STICK,
|
||||
MAX8925_IRQ_TSC_NSTICK,
|
||||
MAX8925_NR_IRQS,
|
||||
};
|
||||
|
||||
struct max8925_chip {
|
||||
struct device *dev;
|
||||
struct i2c_client *i2c;
|
||||
struct i2c_client *adc;
|
||||
struct i2c_client *rtc;
|
||||
struct mutex io_lock;
|
||||
struct mutex irq_lock;
|
||||
|
||||
int irq_base;
|
||||
int core_irq;
|
||||
int tsc_irq;
|
||||
};
|
||||
|
||||
struct max8925_backlight_pdata {
|
||||
int lxw_scl; /* 0/1 -- 0.8Ohm/0.4Ohm */
|
||||
int lxw_freq; /* 700KHz ~ 1400KHz */
|
||||
int dual_string; /* 0/1 -- single/dual string */
|
||||
};
|
||||
|
||||
struct max8925_touch_pdata {
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
struct max8925_power_pdata {
|
||||
int (*set_charger)(int);
|
||||
unsigned batt_detect:1;
|
||||
unsigned topoff_threshold:2;
|
||||
unsigned fast_charge:3; /* charge current */
|
||||
};
|
||||
|
||||
/*
|
||||
* irq_base: stores IRQ base number of MAX8925 in platform
|
||||
* tsc_irq: stores IRQ number of MAX8925 TSC
|
||||
*/
|
||||
struct max8925_platform_data {
|
||||
struct max8925_backlight_pdata *backlight;
|
||||
struct max8925_touch_pdata *touch;
|
||||
struct max8925_power_pdata *power;
|
||||
struct regulator_init_data *regulator[MAX8925_MAX_REGULATOR];
|
||||
|
||||
int irq_base;
|
||||
int tsc_irq;
|
||||
};
|
||||
|
||||
extern int max8925_reg_read(struct i2c_client *, int);
|
||||
extern int max8925_reg_write(struct i2c_client *, int, unsigned char);
|
||||
extern int max8925_bulk_read(struct i2c_client *, int, int, unsigned char *);
|
||||
extern int max8925_bulk_write(struct i2c_client *, int, int, unsigned char *);
|
||||
extern int max8925_set_bits(struct i2c_client *, int, unsigned char,
|
||||
unsigned char);
|
||||
|
||||
extern int max8925_device_init(struct max8925_chip *,
|
||||
struct max8925_platform_data *);
|
||||
extern void max8925_device_exit(struct max8925_chip *);
|
||||
#endif /* __LINUX_MFD_MAX8925_H */
|
||||
|
|
@ -59,7 +59,8 @@ void tmio_core_mmc_clk_div(void __iomem *cnf, int shift, int state);
|
|||
* data for the MMC controller
|
||||
*/
|
||||
struct tmio_mmc_data {
|
||||
const unsigned int hclk;
|
||||
unsigned int hclk;
|
||||
unsigned long capabilities;
|
||||
void (*set_pwr)(struct platform_device *host, int state);
|
||||
void (*set_clk_div)(struct platform_device *host, int state);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <linux/mfd/mcp.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/semaphore.h>
|
||||
|
||||
#define UCB_IO_DATA 0x00
|
||||
#define UCB_IO_DIR 0x01
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#ifndef __MFD_WM831X_CORE_H__
|
||||
#define __MFD_WM831X_CORE_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
/*
|
||||
|
@ -254,9 +255,14 @@ struct wm831x {
|
|||
int irq_masks_cur[WM831X_NUM_IRQ_REGS]; /* Currently active value */
|
||||
int irq_masks_cache[WM831X_NUM_IRQ_REGS]; /* Cached hardware value */
|
||||
|
||||
/* Chip revision based flags */
|
||||
unsigned has_gpio_ena:1; /* Has GPIO enable bit */
|
||||
unsigned has_cs_sts:1; /* Has current sink status bit */
|
||||
|
||||
int num_gpio;
|
||||
|
||||
struct mutex auxadc_lock;
|
||||
struct completion auxadc_done;
|
||||
|
||||
/* The WM831x has a security key blocking access to certain
|
||||
* registers. The mutex is taken by the accessors for locking
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
#define WM831X_GPN_OD_MASK 0x0200 /* GPN_OD */
|
||||
#define WM831X_GPN_OD_SHIFT 9 /* GPN_OD */
|
||||
#define WM831X_GPN_OD_WIDTH 1 /* GPN_OD */
|
||||
#define WM831X_GPN_ENA 0x0080 /* GPN_ENA */
|
||||
#define WM831X_GPN_ENA_MASK 0x0080 /* GPN_ENA */
|
||||
#define WM831X_GPN_ENA_SHIFT 7 /* GPN_ENA */
|
||||
#define WM831X_GPN_ENA_WIDTH 1 /* GPN_ENA */
|
||||
#define WM831X_GPN_TRI 0x0080 /* GPN_TRI */
|
||||
#define WM831X_GPN_TRI_MASK 0x0080 /* GPN_TRI */
|
||||
#define WM831X_GPN_TRI_SHIFT 7 /* GPN_TRI */
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include <linux/mfd/wm8350/audio.h>
|
||||
#include <linux/mfd/wm8350/gpio.h>
|
||||
|
@ -579,6 +580,8 @@
|
|||
|
||||
#define WM8350_NUM_IRQ 63
|
||||
|
||||
#define WM8350_NUM_IRQ_REGS 7
|
||||
|
||||
struct wm8350_reg_access {
|
||||
u16 readable; /* Mask of readable bits */
|
||||
u16 writable; /* Mask of writable bits */
|
||||
|
@ -600,11 +603,6 @@ extern const u16 wm8352_mode3_defaults[];
|
|||
|
||||
struct wm8350;
|
||||
|
||||
struct wm8350_irq {
|
||||
irq_handler_t handler;
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct wm8350_hwmon {
|
||||
struct platform_device *pdev;
|
||||
struct device *classdev;
|
||||
|
@ -624,11 +622,13 @@ struct wm8350 {
|
|||
u16 *reg_cache;
|
||||
|
||||
struct mutex auxadc_mutex;
|
||||
struct completion auxadc_done;
|
||||
|
||||
/* Interrupt handling */
|
||||
struct mutex irq_mutex; /* IRQ table mutex */
|
||||
struct wm8350_irq irq[WM8350_NUM_IRQ];
|
||||
struct mutex irq_lock;
|
||||
int chip_irq;
|
||||
int irq_base;
|
||||
u16 irq_masks[WM8350_NUM_IRQ_REGS];
|
||||
|
||||
/* Client devices */
|
||||
struct wm8350_codec codec;
|
||||
|
@ -647,11 +647,13 @@ struct wm8350 {
|
|||
* used by the platform to configure GPIO functions and similar.
|
||||
* @irq_high: Set if WM8350 IRQ is active high.
|
||||
* @irq_base: Base IRQ for genirq (not currently used).
|
||||
* @gpio_base: Base for gpiolib.
|
||||
*/
|
||||
struct wm8350_platform_data {
|
||||
int (*init)(struct wm8350 *wm8350);
|
||||
int irq_high;
|
||||
int irq_base;
|
||||
int gpio_base;
|
||||
};
|
||||
|
||||
|
||||
|
@ -677,12 +679,33 @@ int wm8350_block_write(struct wm8350 *wm8350, int reg, int size, u16 *src);
|
|||
/*
|
||||
* WM8350 internal interrupts
|
||||
*/
|
||||
int wm8350_register_irq(struct wm8350 *wm8350, int irq,
|
||||
irq_handler_t handler, unsigned long flags,
|
||||
const char *name, void *data);
|
||||
int wm8350_free_irq(struct wm8350 *wm8350, int irq);
|
||||
int wm8350_mask_irq(struct wm8350 *wm8350, int irq);
|
||||
int wm8350_unmask_irq(struct wm8350 *wm8350, int irq);
|
||||
static inline int wm8350_register_irq(struct wm8350 *wm8350, int irq,
|
||||
irq_handler_t handler,
|
||||
unsigned long flags,
|
||||
const char *name, void *data)
|
||||
{
|
||||
if (!wm8350->irq_base)
|
||||
return -ENODEV;
|
||||
|
||||
return request_threaded_irq(irq + wm8350->irq_base, NULL,
|
||||
handler, flags, name, data);
|
||||
}
|
||||
|
||||
static inline void wm8350_free_irq(struct wm8350 *wm8350, int irq, void *data)
|
||||
{
|
||||
free_irq(irq + wm8350->irq_base, data);
|
||||
}
|
||||
|
||||
static inline void wm8350_mask_irq(struct wm8350 *wm8350, int irq)
|
||||
{
|
||||
disable_irq(irq + wm8350->irq_base);
|
||||
}
|
||||
|
||||
static inline void wm8350_unmask_irq(struct wm8350 *wm8350, int irq)
|
||||
{
|
||||
enable_irq(irq + wm8350->irq_base);
|
||||
}
|
||||
|
||||
int wm8350_irq_init(struct wm8350 *wm8350, int irq,
|
||||
struct wm8350_platform_data *pdata);
|
||||
int wm8350_irq_exit(struct wm8350 *wm8350);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#define WM8350_GPIO_FUNCTION_SELECT_2 0x8D
|
||||
#define WM8350_GPIO_FUNCTION_SELECT_3 0x8E
|
||||
#define WM8350_GPIO_FUNCTION_SELECT_4 0x8F
|
||||
#define WM8350_GPIO_LEVEL 0xE6
|
||||
|
||||
/*
|
||||
* GPIO Functions
|
||||
|
|
|
@ -263,6 +263,7 @@ struct wm8350_rtc {
|
|||
struct platform_device *pdev;
|
||||
struct rtc_device *rtc;
|
||||
int alarm_enabled; /* used over suspend/resume */
|
||||
int update_enabled;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* include/linux/mfd/wm8994/core.h -- Core interface for WM8994
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MFD_WM8994_CORE_H__
|
||||
#define __MFD_WM8994_CORE_H__
|
||||
|
||||
struct regulator_dev;
|
||||
struct regulator_bulk_data;
|
||||
|
||||
#define WM8994_NUM_GPIO_REGS 11
|
||||
#define WM8994_NUM_LDO_REGS 2
|
||||
|
||||
struct wm8994 {
|
||||
struct mutex io_lock;
|
||||
|
||||
struct device *dev;
|
||||
int (*read_dev)(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *dest);
|
||||
int (*write_dev)(struct wm8994 *wm8994, unsigned short reg,
|
||||
int bytes, void *src);
|
||||
|
||||
void *control_data;
|
||||
|
||||
int gpio_base;
|
||||
|
||||
/* Used over suspend/resume */
|
||||
u16 ldo_regs[WM8994_NUM_LDO_REGS];
|
||||
u16 gpio_regs[WM8994_NUM_GPIO_REGS];
|
||||
|
||||
struct regulator_dev *dbvdd;
|
||||
struct regulator_bulk_data *supplies;
|
||||
};
|
||||
|
||||
/* Device I/O API */
|
||||
int wm8994_reg_read(struct wm8994 *wm8994, unsigned short reg);
|
||||
int wm8994_reg_write(struct wm8994 *wm8994, unsigned short reg,
|
||||
unsigned short val);
|
||||
int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg,
|
||||
unsigned short mask, unsigned short val);
|
||||
int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg,
|
||||
int count, u16 *buf);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* include/linux/mfd/wm8994/gpio.h - GPIO configuration for WM8994
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MFD_WM8994_GPIO_H__
|
||||
#define __MFD_WM8994_GPIO_H__
|
||||
|
||||
#define WM8994_GPIO_MAX 11
|
||||
|
||||
#define WM8994_GP_FN_PIN_SPECIFIC 0
|
||||
#define WM8994_GP_FN_GPIO 1
|
||||
#define WM8994_GP_FN_SDOUT 2
|
||||
#define WM8994_GP_FN_IRQ 3
|
||||
#define WM8994_GP_FN_TEMPERATURE 4
|
||||
#define WM8994_GP_FN_MICBIAS1_DET 5
|
||||
#define WM8994_GP_FN_MICBIAS1_SHORT 6
|
||||
#define WM8994_GP_FN_MICBIAS2_DET 7
|
||||
#define WM8994_GP_FN_MICBIAS2_SHORT 8
|
||||
#define WM8994_GP_FN_FLL1_LOCK 9
|
||||
#define WM8994_GP_FN_FLL2_LOCK 10
|
||||
#define WM8994_GP_FN_SRC1_LOCK 11
|
||||
#define WM8994_GP_FN_SRC2_LOCK 12
|
||||
#define WM8994_GP_FN_DRC1_ACT 13
|
||||
#define WM8994_GP_FN_DRC2_ACT 14
|
||||
#define WM8994_GP_FN_DRC3_ACT 15
|
||||
#define WM8994_GP_FN_WSEQ_STATUS 16
|
||||
#define WM8994_GP_FN_FIFO_ERROR 17
|
||||
#define WM8994_GP_FN_OPCLK 18
|
||||
|
||||
#define WM8994_GPN_DIR 0x8000 /* GPN_DIR */
|
||||
#define WM8994_GPN_DIR_MASK 0x8000 /* GPN_DIR */
|
||||
#define WM8994_GPN_DIR_SHIFT 15 /* GPN_DIR */
|
||||
#define WM8994_GPN_DIR_WIDTH 1 /* GPN_DIR */
|
||||
#define WM8994_GPN_PU 0x4000 /* GPN_PU */
|
||||
#define WM8994_GPN_PU_MASK 0x4000 /* GPN_PU */
|
||||
#define WM8994_GPN_PU_SHIFT 14 /* GPN_PU */
|
||||
#define WM8994_GPN_PU_WIDTH 1 /* GPN_PU */
|
||||
#define WM8994_GPN_PD 0x2000 /* GPN_PD */
|
||||
#define WM8994_GPN_PD_MASK 0x2000 /* GPN_PD */
|
||||
#define WM8994_GPN_PD_SHIFT 13 /* GPN_PD */
|
||||
#define WM8994_GPN_PD_WIDTH 1 /* GPN_PD */
|
||||
#define WM8994_GPN_POL 0x0400 /* GPN_POL */
|
||||
#define WM8994_GPN_POL_MASK 0x0400 /* GPN_POL */
|
||||
#define WM8994_GPN_POL_SHIFT 10 /* GPN_POL */
|
||||
#define WM8994_GPN_POL_WIDTH 1 /* GPN_POL */
|
||||
#define WM8994_GPN_OP_CFG 0x0200 /* GPN_OP_CFG */
|
||||
#define WM8994_GPN_OP_CFG_MASK 0x0200 /* GPN_OP_CFG */
|
||||
#define WM8994_GPN_OP_CFG_SHIFT 9 /* GPN_OP_CFG */
|
||||
#define WM8994_GPN_OP_CFG_WIDTH 1 /* GPN_OP_CFG */
|
||||
#define WM8994_GPN_DB 0x0100 /* GPN_DB */
|
||||
#define WM8994_GPN_DB_MASK 0x0100 /* GPN_DB */
|
||||
#define WM8994_GPN_DB_SHIFT 8 /* GPN_DB */
|
||||
#define WM8994_GPN_DB_WIDTH 1 /* GPN_DB */
|
||||
#define WM8994_GPN_LVL 0x0040 /* GPN_LVL */
|
||||
#define WM8994_GPN_LVL_MASK 0x0040 /* GPN_LVL */
|
||||
#define WM8994_GPN_LVL_SHIFT 6 /* GPN_LVL */
|
||||
#define WM8994_GPN_LVL_WIDTH 1 /* GPN_LVL */
|
||||
#define WM8994_GPN_FN_MASK 0x001F /* GPN_FN - [4:0] */
|
||||
#define WM8994_GPN_FN_SHIFT 0 /* GPN_FN - [4:0] */
|
||||
#define WM8994_GPN_FN_WIDTH 5 /* GPN_FN - [4:0] */
|
||||
|
||||
#endif
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* include/linux/mfd/wm8994/pdata.h -- Platform data for WM8994
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MFD_WM8994_PDATA_H__
|
||||
#define __MFD_WM8994_PDATA_H__
|
||||
|
||||
#define WM8994_NUM_LDO 2
|
||||
#define WM8994_NUM_GPIO 11
|
||||
|
||||
struct wm8994_ldo_pdata {
|
||||
/** GPIOs to enable regulator, 0 or less if not available */
|
||||
int enable;
|
||||
|
||||
const char *supply;
|
||||
struct regulator_init_data *init_data;
|
||||
};
|
||||
|
||||
#define WM8994_CONFIGURE_GPIO 0x8000
|
||||
|
||||
#define WM8994_DRC_REGS 5
|
||||
#define WM8994_EQ_REGS 19
|
||||
|
||||
/**
|
||||
* DRC configurations are specified with a label and a set of register
|
||||
* values to write (the enable bits will be ignored). At runtime an
|
||||
* enumerated control will be presented for each DRC block allowing
|
||||
* the user to choose the configration to use.
|
||||
*
|
||||
* Configurations may be generated by hand or by using the DRC control
|
||||
* panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
|
||||
* for details.
|
||||
*/
|
||||
struct wm8994_drc_cfg {
|
||||
const char *name;
|
||||
u16 regs[WM8994_DRC_REGS];
|
||||
};
|
||||
|
||||
/**
|
||||
* ReTune Mobile configurations are specified with a label, sample
|
||||
* rate and set of values to write (the enable bits will be ignored).
|
||||
*
|
||||
* Configurations are expected to be generated using the ReTune Mobile
|
||||
* control panel in WISCE - see http://www.wolfsonmicro.com/wisce/
|
||||
*/
|
||||
struct wm8994_retune_mobile_cfg {
|
||||
const char *name;
|
||||
unsigned int rate;
|
||||
u16 regs[WM8994_EQ_REGS];
|
||||
};
|
||||
|
||||
struct wm8994_pdata {
|
||||
int gpio_base;
|
||||
|
||||
/**
|
||||
* Default values for GPIOs if non-zero, WM8994_CONFIGURE_GPIO
|
||||
* can be used for all zero values.
|
||||
*/
|
||||
int gpio_defaults[WM8994_NUM_GPIO];
|
||||
|
||||
struct wm8994_ldo_pdata ldo[WM8994_NUM_LDO];
|
||||
|
||||
|
||||
int num_drc_cfgs;
|
||||
struct wm8994_drc_cfg *drc_cfgs;
|
||||
|
||||
int num_retune_mobile_cfgs;
|
||||
struct wm8994_retune_mobile_cfg *retune_mobile_cfgs;
|
||||
|
||||
/* LINEOUT can be differential or single ended */
|
||||
unsigned int lineout1_diff:1;
|
||||
unsigned int lineout2_diff:1;
|
||||
|
||||
/* Common mode feedback */
|
||||
unsigned int lineout1fb:1;
|
||||
unsigned int lineout2fb:1;
|
||||
|
||||
/* Microphone biases: 0=0.9*AVDD1 1=0.65*AVVD1 */
|
||||
unsigned int micbias1_lvl:1;
|
||||
unsigned int micbias2_lvl:1;
|
||||
|
||||
/* Jack detect threashold levels, see datasheet for values */
|
||||
unsigned int jd_scthr:2;
|
||||
unsigned int jd_thr:2;
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -1349,7 +1349,7 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
|
|||
int mask;
|
||||
struct wm8350_jack_data *jack = NULL;
|
||||
|
||||
switch (irq) {
|
||||
switch (irq - wm8350->irq_base) {
|
||||
case WM8350_IRQ_CODEC_JCK_DET_L:
|
||||
jack = &priv->hpl;
|
||||
mask = WM8350_JACK_L_LVL;
|
||||
|
@ -1424,7 +1424,7 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
|
|||
wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
|
||||
|
||||
/* Sync status */
|
||||
wm8350_hp_jack_handler(irq, priv);
|
||||
wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1521,8 +1521,8 @@ static int wm8350_remove(struct platform_device *pdev)
|
|||
WM8350_JDL_ENA | WM8350_JDR_ENA);
|
||||
wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
|
||||
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv);
|
||||
wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv);
|
||||
|
||||
priv->hpl.jack = NULL;
|
||||
priv->hpr.jack = NULL;
|
||||
|
|
Loading…
Reference in New Issue