Merge branch 'for-next' of git://git.o-hand.com/linux-mfd
* 'for-next' of git://git.o-hand.com/linux-mfd: mfd: Fix twl4030-core build mfd: Ensure sm501 GPIO pin mode is GPIO when configured mfd: dm355 evm MMC/SD card detection regulator: PCF50633 pmic driver input: PCF50633 input driver power_supply: PCF50633 battery charger driver rtc: PCF50633 rtc driver mfd: PCF50633 gpio support mfd: PCF50633 adc driver mfd: PCF50633 core driver
This commit is contained in:
commit
b743791639
|
@ -220,4 +220,11 @@ config HP_SDC_RTC
|
|||
Say Y here if you want to support the built-in real time clock
|
||||
of the HP SDC controller.
|
||||
|
||||
config INPUT_PCF50633_PMU
|
||||
tristate "PCF50633 PMU events"
|
||||
depends on MFD_PCF50633
|
||||
help
|
||||
Say Y to include support for delivering PMU events via input
|
||||
layer on NXP PCF50633.
|
||||
|
||||
endif
|
||||
|
|
|
@ -21,3 +21,4 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
|
|||
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
|
||||
obj-$(CONFIG_INPUT_APANEL) += apanel.o
|
||||
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
|
||||
obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/* NXP PCF50633 Input Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte, Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
|
||||
#define PCF50633_OOCSTAT_ONKEY 0x01
|
||||
#define PCF50633_REG_OOCSTAT 0x12
|
||||
#define PCF50633_REG_OOCMODE 0x10
|
||||
|
||||
struct pcf50633_input {
|
||||
struct pcf50633 *pcf;
|
||||
struct input_dev *input_dev;
|
||||
};
|
||||
|
||||
static void
|
||||
pcf50633_input_irq(int irq, void *data)
|
||||
{
|
||||
struct pcf50633_input *input;
|
||||
int onkey_released;
|
||||
|
||||
input = data;
|
||||
|
||||
/* We report only one event depending on the key press status */
|
||||
onkey_released = pcf50633_reg_read(input->pcf, PCF50633_REG_OOCSTAT)
|
||||
& PCF50633_OOCSTAT_ONKEY;
|
||||
|
||||
if (irq == PCF50633_IRQ_ONKEYF && !onkey_released)
|
||||
input_report_key(input->input_dev, KEY_POWER, 1);
|
||||
else if (irq == PCF50633_IRQ_ONKEYR && onkey_released)
|
||||
input_report_key(input->input_dev, KEY_POWER, 0);
|
||||
|
||||
input_sync(input->input_dev);
|
||||
}
|
||||
|
||||
static int __devinit pcf50633_input_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_input *input;
|
||||
struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data;
|
||||
struct input_dev *input_dev;
|
||||
int ret;
|
||||
|
||||
|
||||
input = kzalloc(sizeof(*input), GFP_KERNEL);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input_dev = input_allocate_device();
|
||||
if (!input_dev) {
|
||||
kfree(input);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, input);
|
||||
input->pcf = pdata->pcf;
|
||||
input->input_dev = input_dev;
|
||||
|
||||
input_dev->name = "PCF50633 PMU events";
|
||||
input_dev->id.bustype = BUS_I2C;
|
||||
input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR);
|
||||
set_bit(KEY_POWER, input_dev->keybit);
|
||||
|
||||
ret = input_register_device(input_dev);
|
||||
if (ret) {
|
||||
input_free_device(input_dev);
|
||||
kfree(input);
|
||||
return ret;
|
||||
}
|
||||
pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ONKEYR,
|
||||
pcf50633_input_irq, input);
|
||||
pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ONKEYF,
|
||||
pcf50633_input_irq, input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_input_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_input *input = platform_get_drvdata(pdev);
|
||||
|
||||
pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYR);
|
||||
pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYF);
|
||||
|
||||
input_unregister_device(input->input_dev);
|
||||
kfree(input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pcf50633_input_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633-input",
|
||||
},
|
||||
.probe = pcf50633_input_probe,
|
||||
.remove = __devexit_p(pcf50633_input_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_input_init(void)
|
||||
{
|
||||
return platform_driver_register(&pcf50633_input_driver);
|
||||
}
|
||||
module_init(pcf50633_input_init);
|
||||
|
||||
static void __exit pcf50633_input_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pcf50633_input_driver);
|
||||
}
|
||||
module_exit(pcf50633_input_exit);
|
||||
|
||||
MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
|
||||
MODULE_DESCRIPTION("PCF50633 input driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pcf50633-input");
|
|
@ -217,6 +217,29 @@ 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_PCF50633
|
||||
tristate "Support for NXP PCF50633"
|
||||
depends on I2C
|
||||
help
|
||||
Say yes here if you have NXP PCF50633 chip on your board.
|
||||
This core driver provides register access and IRQ handling
|
||||
facilities, and registers devices for the various functions
|
||||
so that function-specific drivers can bind to them.
|
||||
|
||||
config PCF50633_ADC
|
||||
tristate "Support for NXP PCF50633 ADC"
|
||||
depends on MFD_PCF50633
|
||||
help
|
||||
Say yes here if you want to include support for ADC in the
|
||||
NXP PCF50633 chip.
|
||||
|
||||
config PCF50633_GPIO
|
||||
tristate "Support for NXP PCF50633 GPIO"
|
||||
depends on MFD_PCF50633
|
||||
help
|
||||
Say yes here if you want to include support GPIO for pins on
|
||||
the PCF50633 chip.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Multimedia Capabilities Port drivers"
|
||||
|
|
|
@ -37,3 +37,7 @@ endif
|
|||
obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
|
||||
|
||||
obj-$(CONFIG_PMIC_DA903X) += da903x.o
|
||||
|
||||
obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
|
||||
obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o
|
||||
obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o
|
|
@ -107,6 +107,9 @@ static const u8 msp_gpios[] = {
|
|||
MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1),
|
||||
MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1),
|
||||
MSP_GPIO(4, SWITCH1),
|
||||
/* switches on MMC/SD sockets */
|
||||
MSP_GPIO(1, SDMMC), MSP_GPIO(2, SDMMC), /* mmc0 WP, nCD */
|
||||
MSP_GPIO(3, SDMMC), MSP_GPIO(4, SDMMC), /* mmc1 WP, nCD */
|
||||
};
|
||||
|
||||
#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3)
|
||||
|
@ -304,6 +307,13 @@ static int add_children(struct i2c_client *client)
|
|||
gpio_export(gpio, false);
|
||||
}
|
||||
|
||||
/* MMC/SD inputs -- right after the last config input */
|
||||
if (client->dev.platform_data) {
|
||||
void (*mmcsd_setup)(unsigned) = client->dev.platform_data;
|
||||
|
||||
mmcsd_setup(dm355evm_msp_gpio.base + 8 + 5);
|
||||
}
|
||||
|
||||
/* RTC is a 32 bit counter, no alarm */
|
||||
if (msp_has_rtc()) {
|
||||
child = add_child(client, "rtc-dm355evm",
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
/* NXP PCF50633 ADC Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte, Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* NOTE: This driver does not yet support subtractive ADC mode, which means
|
||||
* you can do only one measurement per read request.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/mfd/pcf50633/adc.h>
|
||||
|
||||
struct pcf50633_adc_request {
|
||||
int mux;
|
||||
int avg;
|
||||
int result;
|
||||
void (*callback)(struct pcf50633 *, void *, int);
|
||||
void *callback_param;
|
||||
|
||||
/* Used in case of sync requests */
|
||||
struct completion completion;
|
||||
|
||||
};
|
||||
|
||||
#define PCF50633_MAX_ADC_FIFO_DEPTH 8
|
||||
|
||||
struct pcf50633_adc {
|
||||
struct pcf50633 *pcf;
|
||||
|
||||
/* Private stuff */
|
||||
struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH];
|
||||
int queue_head;
|
||||
int queue_tail;
|
||||
struct mutex queue_mutex;
|
||||
};
|
||||
|
||||
static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf)
|
||||
{
|
||||
return platform_get_drvdata(pcf->adc_pdev);
|
||||
}
|
||||
|
||||
static void adc_setup(struct pcf50633 *pcf, int channel, int avg)
|
||||
{
|
||||
channel &= PCF50633_ADCC1_ADCMUX_MASK;
|
||||
|
||||
/* kill ratiometric, but enable ACCSW biasing */
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
|
||||
|
||||
/* start ADC conversion on selected channel */
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
|
||||
PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
|
||||
}
|
||||
|
||||
static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
|
||||
{
|
||||
struct pcf50633_adc *adc = __to_adc(pcf);
|
||||
int head;
|
||||
|
||||
mutex_lock(&adc->queue_mutex);
|
||||
|
||||
head = adc->queue_head;
|
||||
|
||||
if (!adc->queue[head]) {
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
|
||||
adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg);
|
||||
}
|
||||
|
||||
static int
|
||||
adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
|
||||
{
|
||||
struct pcf50633_adc *adc = __to_adc(pcf);
|
||||
int head, tail;
|
||||
|
||||
mutex_lock(&adc->queue_mutex);
|
||||
|
||||
head = adc->queue_head;
|
||||
tail = adc->queue_tail;
|
||||
|
||||
if (adc->queue[tail]) {
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
adc->queue[tail] = req;
|
||||
adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
|
||||
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
|
||||
trigger_next_adc_job_if_any(pcf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
|
||||
{
|
||||
struct pcf50633_adc_request *req = param;
|
||||
|
||||
req->result = result;
|
||||
complete(&req->completion);
|
||||
}
|
||||
|
||||
int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
|
||||
{
|
||||
struct pcf50633_adc_request *req;
|
||||
|
||||
/* req is freed when the result is ready, in interrupt handler */
|
||||
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
req->mux = mux;
|
||||
req->avg = avg;
|
||||
req->callback = pcf50633_adc_sync_read_callback;
|
||||
req->callback_param = req;
|
||||
|
||||
init_completion(&req->completion);
|
||||
adc_enqueue_request(pcf, req);
|
||||
wait_for_completion(&req->completion);
|
||||
|
||||
return req->result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
|
||||
|
||||
int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
|
||||
void (*callback)(struct pcf50633 *, void *, int),
|
||||
void *callback_param)
|
||||
{
|
||||
struct pcf50633_adc_request *req;
|
||||
|
||||
/* req is freed when the result is ready, in interrupt handler */
|
||||
req = kmalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
req->mux = mux;
|
||||
req->avg = avg;
|
||||
req->callback = callback;
|
||||
req->callback_param = callback_param;
|
||||
|
||||
adc_enqueue_request(pcf, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
|
||||
|
||||
static int adc_result(struct pcf50633 *pcf)
|
||||
{
|
||||
u8 adcs1, adcs3;
|
||||
u16 result;
|
||||
|
||||
adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1);
|
||||
adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3);
|
||||
result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK);
|
||||
|
||||
dev_dbg(pcf->dev, "adc result = %d\n", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void pcf50633_adc_irq(int irq, void *data)
|
||||
{
|
||||
struct pcf50633_adc *adc = data;
|
||||
struct pcf50633 *pcf = adc->pcf;
|
||||
struct pcf50633_adc_request *req;
|
||||
int head;
|
||||
|
||||
mutex_lock(&adc->queue_mutex);
|
||||
head = adc->queue_head;
|
||||
|
||||
req = adc->queue[head];
|
||||
if (WARN_ON(!req)) {
|
||||
dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n");
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
return;
|
||||
}
|
||||
adc->queue[head] = NULL;
|
||||
adc->queue_head = (head + 1) &
|
||||
(PCF50633_MAX_ADC_FIFO_DEPTH - 1);
|
||||
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
|
||||
req->callback(pcf, req->callback_param, adc_result(pcf));
|
||||
kfree(req);
|
||||
|
||||
trigger_next_adc_job_if_any(pcf);
|
||||
}
|
||||
|
||||
static int __devinit pcf50633_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data;
|
||||
struct pcf50633_adc *adc;
|
||||
|
||||
adc = kzalloc(sizeof(*adc), GFP_KERNEL);
|
||||
if (!adc)
|
||||
return -ENOMEM;
|
||||
|
||||
adc->pcf = pdata->pcf;
|
||||
platform_set_drvdata(pdev, adc);
|
||||
|
||||
pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ADCRDY,
|
||||
pcf50633_adc_irq, adc);
|
||||
|
||||
mutex_init(&adc->queue_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_adc *adc = platform_get_drvdata(pdev);
|
||||
int i, head;
|
||||
|
||||
pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY);
|
||||
|
||||
mutex_lock(&adc->queue_mutex);
|
||||
head = adc->queue_head;
|
||||
|
||||
if (WARN_ON(adc->queue[head]))
|
||||
dev_err(adc->pcf->dev,
|
||||
"adc driver removed with request pending\n");
|
||||
|
||||
for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++)
|
||||
kfree(adc->queue[i]);
|
||||
|
||||
mutex_unlock(&adc->queue_mutex);
|
||||
kfree(adc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pcf50633_adc_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633-adc",
|
||||
},
|
||||
.probe = pcf50633_adc_probe,
|
||||
.remove = __devexit_p(pcf50633_adc_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_adc_init(void)
|
||||
{
|
||||
return platform_driver_register(&pcf50633_adc_driver);
|
||||
}
|
||||
module_init(pcf50633_adc_init);
|
||||
|
||||
static void __exit pcf50633_adc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pcf50633_adc_driver);
|
||||
}
|
||||
module_exit(pcf50633_adc_exit);
|
||||
|
||||
MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
|
||||
MODULE_DESCRIPTION("PCF50633 adc driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pcf50633-adc");
|
||||
|
|
@ -0,0 +1,710 @@
|
|||
/* NXP PCF50633 Power Management Unit (PMU) driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Harald Welte <laforge@openmoko.org>
|
||||
* Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
|
||||
/* Two MBCS registers used during cold start */
|
||||
#define PCF50633_REG_MBCS1 0x4b
|
||||
#define PCF50633_REG_MBCS2 0x4c
|
||||
#define PCF50633_MBCS1_USBPRES 0x01
|
||||
#define PCF50633_MBCS1_ADAPTPRES 0x01
|
||||
|
||||
static int __pcf50633_read(struct pcf50633 *pcf, u8 reg, int num, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(pcf->i2c_client, reg,
|
||||
num, data);
|
||||
if (ret < 0)
|
||||
dev_err(pcf->dev, "Error reading %d regs at %d\n", num, reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __pcf50633_write(struct pcf50633 *pcf, u8 reg, int num, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(pcf->i2c_client, reg,
|
||||
num, data);
|
||||
if (ret < 0)
|
||||
dev_err(pcf->dev, "Error writing %d regs at %d\n", num, reg);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/* Read a block of upto 32 regs */
|
||||
int pcf50633_read_block(struct pcf50633 *pcf, u8 reg,
|
||||
int nr_regs, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
ret = __pcf50633_read(pcf, reg, nr_regs, data);
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_read_block);
|
||||
|
||||
/* Write a block of upto 32 regs */
|
||||
int pcf50633_write_block(struct pcf50633 *pcf , u8 reg,
|
||||
int nr_regs, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
ret = __pcf50633_write(pcf, reg, nr_regs, data);
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_write_block);
|
||||
|
||||
u8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
__pcf50633_read(pcf, reg, 1, &val);
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_reg_read);
|
||||
|
||||
int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
ret = __pcf50633_write(pcf, reg, 1, &val);
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_reg_write);
|
||||
|
||||
int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val)
|
||||
{
|
||||
int ret;
|
||||
u8 tmp;
|
||||
|
||||
val &= mask;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
ret = __pcf50633_read(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
tmp &= ~mask;
|
||||
tmp |= val;
|
||||
ret = __pcf50633_write(pcf, reg, 1, &tmp);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask);
|
||||
|
||||
int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val)
|
||||
{
|
||||
int ret;
|
||||
u8 tmp;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
ret = __pcf50633_read(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
tmp &= ~val;
|
||||
ret = __pcf50633_write(pcf, reg, 1, &tmp);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits);
|
||||
|
||||
/* sysfs attributes */
|
||||
static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct pcf50633 *pcf = dev_get_drvdata(dev);
|
||||
u8 dump[16];
|
||||
int n, n1, idx = 0;
|
||||
char *buf1 = buf;
|
||||
static u8 address_no_read[] = { /* must be ascending */
|
||||
PCF50633_REG_INT1,
|
||||
PCF50633_REG_INT2,
|
||||
PCF50633_REG_INT3,
|
||||
PCF50633_REG_INT4,
|
||||
PCF50633_REG_INT5,
|
||||
0 /* terminator */
|
||||
};
|
||||
|
||||
for (n = 0; n < 256; n += sizeof(dump)) {
|
||||
for (n1 = 0; n1 < sizeof(dump); n1++)
|
||||
if (n == address_no_read[idx]) {
|
||||
idx++;
|
||||
dump[n1] = 0x00;
|
||||
} else
|
||||
dump[n1] = pcf50633_reg_read(pcf, n + n1);
|
||||
|
||||
hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0);
|
||||
buf1 += strlen(buf1);
|
||||
*buf1++ = '\n';
|
||||
*buf1 = '\0';
|
||||
}
|
||||
|
||||
return buf1 - buf;
|
||||
}
|
||||
static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
|
||||
|
||||
static ssize_t show_resume_reason(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct pcf50633 *pcf = dev_get_drvdata(dev);
|
||||
int n;
|
||||
|
||||
n = sprintf(buf, "%02x%02x%02x%02x%02x\n",
|
||||
pcf->resume_reason[0],
|
||||
pcf->resume_reason[1],
|
||||
pcf->resume_reason[2],
|
||||
pcf->resume_reason[3],
|
||||
pcf->resume_reason[4]);
|
||||
|
||||
return n;
|
||||
}
|
||||
static DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL);
|
||||
|
||||
static struct attribute *pcf_sysfs_entries[] = {
|
||||
&dev_attr_dump_regs.attr,
|
||||
&dev_attr_resume_reason.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group pcf_attr_group = {
|
||||
.name = NULL, /* put in device directory */
|
||||
.attrs = pcf_sysfs_entries,
|
||||
};
|
||||
|
||||
int pcf50633_register_irq(struct pcf50633 *pcf, int irq,
|
||||
void (*handler) (int, void *), void *data)
|
||||
{
|
||||
if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler)
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(pcf->irq_handler[irq].handler))
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
pcf->irq_handler[irq].handler = handler;
|
||||
pcf->irq_handler[irq].data = data;
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_register_irq);
|
||||
|
||||
int pcf50633_free_irq(struct pcf50633 *pcf, int irq)
|
||||
{
|
||||
if (irq < 0 || irq > PCF50633_NUM_IRQ)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
pcf->irq_handler[irq].handler = NULL;
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_free_irq);
|
||||
|
||||
static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask)
|
||||
{
|
||||
u8 reg, bits, tmp;
|
||||
int ret = 0, idx;
|
||||
|
||||
idx = irq >> 3;
|
||||
reg = PCF50633_REG_INT1M + idx;
|
||||
bits = 1 << (irq & 0x07);
|
||||
|
||||
mutex_lock(&pcf->lock);
|
||||
|
||||
if (mask) {
|
||||
ret = __pcf50633_read(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
tmp |= bits;
|
||||
|
||||
ret = __pcf50633_write(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pcf->mask_regs[idx] &= ~bits;
|
||||
pcf->mask_regs[idx] |= bits;
|
||||
} else {
|
||||
ret = __pcf50633_read(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
tmp &= ~bits;
|
||||
|
||||
ret = __pcf50633_write(pcf, reg, 1, &tmp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pcf->mask_regs[idx] &= ~bits;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&pcf->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pcf50633_irq_mask(struct pcf50633 *pcf, int irq)
|
||||
{
|
||||
dev_info(pcf->dev, "Masking IRQ %d\n", irq);
|
||||
|
||||
return __pcf50633_irq_mask_set(pcf, irq, 1);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_irq_mask);
|
||||
|
||||
int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq)
|
||||
{
|
||||
dev_info(pcf->dev, "Unmasking IRQ %d\n", irq);
|
||||
|
||||
return __pcf50633_irq_mask_set(pcf, irq, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_irq_unmask);
|
||||
|
||||
int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq)
|
||||
{
|
||||
u8 reg, bits;
|
||||
|
||||
reg = irq >> 3;
|
||||
bits = 1 << (irq & 0x07);
|
||||
|
||||
return pcf->mask_regs[reg] & bits;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get);
|
||||
|
||||
static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq)
|
||||
{
|
||||
if (pcf->irq_handler[irq].handler)
|
||||
pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data);
|
||||
}
|
||||
|
||||
/* Maximum amount of time ONKEY is held before emergency action is taken */
|
||||
#define PCF50633_ONKEY1S_TIMEOUT 8
|
||||
|
||||
static void pcf50633_irq_worker(struct work_struct *work)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
int ret, i, j;
|
||||
u8 pcf_int[5], chgstat;
|
||||
|
||||
pcf = container_of(work, struct pcf50633, irq_work);
|
||||
|
||||
/* Read the 5 INT regs in one transaction */
|
||||
ret = pcf50633_read_block(pcf, PCF50633_REG_INT1,
|
||||
ARRAY_SIZE(pcf_int), pcf_int);
|
||||
if (ret != ARRAY_SIZE(pcf_int)) {
|
||||
dev_err(pcf->dev, "Error reading INT registers\n");
|
||||
|
||||
/*
|
||||
* If this doesn't ACK the interrupt to the chip, we'll be
|
||||
* called once again as we're level triggered.
|
||||
*/
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* We immediately read the usb and adapter status. We thus make sure
|
||||
* only of USBINS/USBREM IRQ handlers are called */
|
||||
if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) {
|
||||
chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
|
||||
if (chgstat & (0x3 << 4))
|
||||
pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM);
|
||||
else
|
||||
pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS);
|
||||
}
|
||||
|
||||
/* Make sure only one of ADPINS or ADPREM is set */
|
||||
if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) {
|
||||
chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
|
||||
if (chgstat & (0x3 << 4))
|
||||
pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM);
|
||||
else
|
||||
pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS);
|
||||
}
|
||||
|
||||
dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x "
|
||||
"INT4=0x%02x INT5=0x%02x\n", pcf_int[0],
|
||||
pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]);
|
||||
|
||||
/* Some revisions of the chip don't have a 8s standby mode on
|
||||
* ONKEY1S press. We try to manually do it in such cases. */
|
||||
if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) {
|
||||
dev_info(pcf->dev, "ONKEY1S held for %d secs\n",
|
||||
pcf->onkey1s_held);
|
||||
if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT)
|
||||
if (pcf->pdata->force_shutdown)
|
||||
pcf->pdata->force_shutdown(pcf);
|
||||
}
|
||||
|
||||
if (pcf_int[2] & PCF50633_INT3_ONKEY1S) {
|
||||
dev_info(pcf->dev, "ONKEY1S held\n");
|
||||
pcf->onkey1s_held = 1 ;
|
||||
|
||||
/* Unmask IRQ_SECOND */
|
||||
pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M,
|
||||
PCF50633_INT1_SECOND);
|
||||
|
||||
/* Unmask IRQ_ONKEYR */
|
||||
pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M,
|
||||
PCF50633_INT2_ONKEYR);
|
||||
}
|
||||
|
||||
if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) {
|
||||
pcf->onkey1s_held = 0;
|
||||
|
||||
/* Mask SECOND and ONKEYR interrupts */
|
||||
if (pcf->mask_regs[0] & PCF50633_INT1_SECOND)
|
||||
pcf50633_reg_set_bit_mask(pcf,
|
||||
PCF50633_REG_INT1M,
|
||||
PCF50633_INT1_SECOND,
|
||||
PCF50633_INT1_SECOND);
|
||||
|
||||
if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR)
|
||||
pcf50633_reg_set_bit_mask(pcf,
|
||||
PCF50633_REG_INT2M,
|
||||
PCF50633_INT2_ONKEYR,
|
||||
PCF50633_INT2_ONKEYR);
|
||||
}
|
||||
|
||||
/* Have we just resumed ? */
|
||||
if (pcf->is_suspended) {
|
||||
pcf->is_suspended = 0;
|
||||
|
||||
/* Set the resume reason filtering out non resumers */
|
||||
for (i = 0; i < ARRAY_SIZE(pcf_int); i++)
|
||||
pcf->resume_reason[i] = pcf_int[i] &
|
||||
pcf->pdata->resumers[i];
|
||||
|
||||
/* Make sure we don't pass on any ONKEY events to
|
||||
* userspace now */
|
||||
pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pcf_int); i++) {
|
||||
/* Unset masked interrupts */
|
||||
pcf_int[i] &= ~pcf->mask_regs[i];
|
||||
|
||||
for (j = 0; j < 8 ; j++)
|
||||
if (pcf_int[i] & (1 << j))
|
||||
pcf50633_irq_call_handler(pcf, (i * 8) + j);
|
||||
}
|
||||
|
||||
out:
|
||||
put_device(pcf->dev);
|
||||
enable_irq(pcf->irq);
|
||||
}
|
||||
|
||||
static irqreturn_t pcf50633_irq(int irq, void *data)
|
||||
{
|
||||
struct pcf50633 *pcf = data;
|
||||
|
||||
dev_dbg(pcf->dev, "pcf50633_irq\n");
|
||||
|
||||
get_device(pcf->dev);
|
||||
disable_irq(pcf->irq);
|
||||
schedule_work(&pcf->irq_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
|
||||
struct platform_device **pdev)
|
||||
{
|
||||
struct pcf50633_subdev_pdata *subdev_pdata;
|
||||
int ret;
|
||||
|
||||
*pdev = platform_device_alloc(name, -1);
|
||||
if (!*pdev) {
|
||||
dev_err(pcf->dev, "Falied to allocate %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
subdev_pdata = kmalloc(sizeof(*subdev_pdata), GFP_KERNEL);
|
||||
if (!subdev_pdata) {
|
||||
dev_err(pcf->dev, "Error allocating subdev pdata\n");
|
||||
platform_device_put(*pdev);
|
||||
}
|
||||
|
||||
subdev_pdata->pcf = pcf;
|
||||
platform_device_add_data(*pdev, subdev_pdata, sizeof(*subdev_pdata));
|
||||
|
||||
(*pdev)->dev.parent = pcf->dev;
|
||||
|
||||
ret = platform_device_add(*pdev);
|
||||
if (ret) {
|
||||
dev_err(pcf->dev, "Failed to register %s: %d\n", name, ret);
|
||||
platform_device_put(*pdev);
|
||||
*pdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pcf50633_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
int ret = 0, i;
|
||||
u8 res[5];
|
||||
|
||||
pcf = dev_get_drvdata(dev);
|
||||
|
||||
/* Make sure our interrupt handlers are not called
|
||||
* henceforth */
|
||||
disable_irq(pcf->irq);
|
||||
|
||||
/* Make sure that any running IRQ worker has quit */
|
||||
cancel_work_sync(&pcf->irq_work);
|
||||
|
||||
/* Save the masks */
|
||||
ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M,
|
||||
ARRAY_SIZE(pcf->suspend_irq_masks),
|
||||
pcf->suspend_irq_masks);
|
||||
if (ret < 0) {
|
||||
dev_err(pcf->dev, "error saving irq masks\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Write wakeup irq masks */
|
||||
for (i = 0; i < ARRAY_SIZE(res); i++)
|
||||
res[i] = ~pcf->pdata->resumers[i];
|
||||
|
||||
ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
|
||||
ARRAY_SIZE(res), &res[0]);
|
||||
if (ret < 0) {
|
||||
dev_err(pcf->dev, "error writing wakeup irq masks\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
pcf->is_suspended = 1;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pcf50633_resume(struct device *dev)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
int ret;
|
||||
|
||||
pcf = dev_get_drvdata(dev);
|
||||
|
||||
/* Write the saved mask registers */
|
||||
ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
|
||||
ARRAY_SIZE(pcf->suspend_irq_masks),
|
||||
pcf->suspend_irq_masks);
|
||||
if (ret < 0)
|
||||
dev_err(pcf->dev, "Error restoring saved suspend masks\n");
|
||||
|
||||
/* Restore regulators' state */
|
||||
|
||||
|
||||
get_device(pcf->dev);
|
||||
|
||||
/*
|
||||
* Clear any pending interrupts and set resume reason if any.
|
||||
* This will leave with enable_irq()
|
||||
*/
|
||||
pcf50633_irq_worker(&pcf->irq_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define pcf50633_suspend NULL
|
||||
#define pcf50633_resume NULL
|
||||
#endif
|
||||
|
||||
static int __devinit pcf50633_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *ids)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
struct pcf50633_platform_data *pdata = client->dev.platform_data;
|
||||
int i, ret = 0;
|
||||
int version, variant;
|
||||
|
||||
pcf = kzalloc(sizeof(*pcf), GFP_KERNEL);
|
||||
if (!pcf)
|
||||
return -ENOMEM;
|
||||
|
||||
pcf->pdata = pdata;
|
||||
|
||||
mutex_init(&pcf->lock);
|
||||
|
||||
i2c_set_clientdata(client, pcf);
|
||||
pcf->dev = &client->dev;
|
||||
pcf->i2c_client = client;
|
||||
pcf->irq = client->irq;
|
||||
|
||||
INIT_WORK(&pcf->irq_work, pcf50633_irq_worker);
|
||||
|
||||
version = pcf50633_reg_read(pcf, 0);
|
||||
variant = pcf50633_reg_read(pcf, 1);
|
||||
if (version < 0 || variant < 0) {
|
||||
dev_err(pcf->dev, "Unable to probe pcf50633\n");
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev_info(pcf->dev, "Probed device version %d variant %d\n",
|
||||
version, variant);
|
||||
|
||||
/* Enable all interrupts except RTC SECOND */
|
||||
pcf->mask_regs[0] = 0x80;
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]);
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00);
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00);
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00);
|
||||
pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00);
|
||||
|
||||
/* Create sub devices */
|
||||
pcf50633_client_dev_register(pcf, "pcf50633-input",
|
||||
&pcf->input_pdev);
|
||||
pcf50633_client_dev_register(pcf, "pcf50633-rtc",
|
||||
&pcf->rtc_pdev);
|
||||
pcf50633_client_dev_register(pcf, "pcf50633-mbc",
|
||||
&pcf->mbc_pdev);
|
||||
pcf50633_client_dev_register(pcf, "pcf50633-adc",
|
||||
&pcf->adc_pdev);
|
||||
|
||||
for (i = 0; i < PCF50633_NUM_REGULATORS; i++) {
|
||||
struct platform_device *pdev;
|
||||
|
||||
pdev = platform_device_alloc("pcf50633-regltr", i);
|
||||
if (!pdev) {
|
||||
dev_err(pcf->dev, "Cannot create regulator\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
pdev->dev.parent = pcf->dev;
|
||||
pdev->dev.platform_data = &pdata->reg_init_data[i];
|
||||
pdev->dev.driver_data = pcf;
|
||||
pcf->regulator_pdev[i] = pdev;
|
||||
|
||||
platform_device_add(pdev);
|
||||
}
|
||||
|
||||
if (client->irq) {
|
||||
set_irq_handler(client->irq, handle_level_irq);
|
||||
ret = request_irq(client->irq, pcf50633_irq,
|
||||
IRQF_TRIGGER_LOW, "pcf50633", pcf);
|
||||
|
||||
if (ret) {
|
||||
dev_err(pcf->dev, "Failed to request IRQ %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
dev_err(pcf->dev, "No IRQ configured\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (enable_irq_wake(client->irq) < 0)
|
||||
dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source"
|
||||
"in this hardware revision", client->irq);
|
||||
|
||||
ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group);
|
||||
if (ret)
|
||||
dev_err(pcf->dev, "error creating sysfs entries\n");
|
||||
|
||||
if (pdata->probe_done)
|
||||
pdata->probe_done(pcf);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
kfree(pcf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pcf50633 *pcf = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
free_irq(pcf->irq, pcf);
|
||||
|
||||
platform_device_unregister(pcf->input_pdev);
|
||||
platform_device_unregister(pcf->rtc_pdev);
|
||||
platform_device_unregister(pcf->mbc_pdev);
|
||||
platform_device_unregister(pcf->adc_pdev);
|
||||
|
||||
for (i = 0; i < PCF50633_NUM_REGULATORS; i++)
|
||||
platform_device_unregister(pcf->regulator_pdev[i]);
|
||||
|
||||
kfree(pcf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_device_id pcf50633_id_table[] = {
|
||||
{"pcf50633", 0x73},
|
||||
};
|
||||
|
||||
static struct i2c_driver pcf50633_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633",
|
||||
.suspend = pcf50633_suspend,
|
||||
.resume = pcf50633_resume,
|
||||
},
|
||||
.id_table = pcf50633_id_table,
|
||||
.probe = pcf50633_probe,
|
||||
.remove = __devexit_p(pcf50633_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pcf50633_driver);
|
||||
}
|
||||
|
||||
static void __exit pcf50633_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pcf50633_driver);
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU");
|
||||
MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(pcf50633_init);
|
||||
module_exit(pcf50633_exit);
|
|
@ -0,0 +1,118 @@
|
|||
/* NXP PCF50633 GPIO Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte, Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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/mfd/pcf50633/core.h>
|
||||
#include <linux/mfd/pcf50633/gpio.h>
|
||||
|
||||
enum pcf50633_regulator_id {
|
||||
PCF50633_REGULATOR_AUTO,
|
||||
PCF50633_REGULATOR_DOWN1,
|
||||
PCF50633_REGULATOR_DOWN2,
|
||||
PCF50633_REGULATOR_LDO1,
|
||||
PCF50633_REGULATOR_LDO2,
|
||||
PCF50633_REGULATOR_LDO3,
|
||||
PCF50633_REGULATOR_LDO4,
|
||||
PCF50633_REGULATOR_LDO5,
|
||||
PCF50633_REGULATOR_LDO6,
|
||||
PCF50633_REGULATOR_HCLDO,
|
||||
PCF50633_REGULATOR_MEMLDO,
|
||||
};
|
||||
|
||||
#define PCF50633_REG_AUTOOUT 0x1a
|
||||
#define PCF50633_REG_DOWN1OUT 0x1e
|
||||
#define PCF50633_REG_DOWN2OUT 0x22
|
||||
#define PCF50633_REG_MEMLDOOUT 0x26
|
||||
#define PCF50633_REG_LDO1OUT 0x2d
|
||||
#define PCF50633_REG_LDO2OUT 0x2f
|
||||
#define PCF50633_REG_LDO3OUT 0x31
|
||||
#define PCF50633_REG_LDO4OUT 0x33
|
||||
#define PCF50633_REG_LDO5OUT 0x35
|
||||
#define PCF50633_REG_LDO6OUT 0x37
|
||||
#define PCF50633_REG_HCLDOOUT 0x39
|
||||
|
||||
static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = {
|
||||
[PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT,
|
||||
[PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT,
|
||||
[PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT,
|
||||
[PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT,
|
||||
[PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT,
|
||||
[PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT,
|
||||
[PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT,
|
||||
[PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT,
|
||||
[PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT,
|
||||
[PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT,
|
||||
[PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT,
|
||||
};
|
||||
|
||||
int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
||||
|
||||
return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_gpio_set);
|
||||
|
||||
u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio)
|
||||
{
|
||||
u8 reg, val;
|
||||
|
||||
reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
||||
val = pcf50633_reg_read(pcf, reg) & 0x07;
|
||||
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
|
||||
|
||||
int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert)
|
||||
{
|
||||
u8 val, reg;
|
||||
|
||||
reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
||||
val = !!invert << 3;
|
||||
|
||||
return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set);
|
||||
|
||||
int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio)
|
||||
{
|
||||
u8 reg, val;
|
||||
|
||||
reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
|
||||
val = pcf50633_reg_read(pcf, reg);
|
||||
|
||||
return val & (1 << 3);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get);
|
||||
|
||||
int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf,
|
||||
int gpio, int regulator, int on)
|
||||
{
|
||||
u8 reg, val, mask;
|
||||
|
||||
/* the *ENA register is always one after the *OUT register */
|
||||
reg = pcf50633_regulator_registers[regulator] + 1;
|
||||
|
||||
val = !!on << (gpio - PCF50633_GPIO1);
|
||||
mask = 1 << (gpio - PCF50633_GPIO1);
|
||||
|
||||
return pcf50633_reg_set_bit_mask(pcf, reg, mask, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set);
|
|
@ -41,6 +41,7 @@ struct sm501_gpio_chip {
|
|||
struct gpio_chip gpio;
|
||||
struct sm501_gpio *ourgpio; /* to get back to parent. */
|
||||
void __iomem *regbase;
|
||||
void __iomem *control; /* address of control reg. */
|
||||
};
|
||||
|
||||
struct sm501_gpio {
|
||||
|
@ -908,6 +909,25 @@ static int sm501_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|||
return result & 1UL;
|
||||
}
|
||||
|
||||
static void sm501_gpio_ensure_gpio(struct sm501_gpio_chip *smchip,
|
||||
unsigned long bit)
|
||||
{
|
||||
unsigned long ctrl;
|
||||
|
||||
/* check and modify if this pin is not set as gpio. */
|
||||
|
||||
if (readl(smchip->control) & bit) {
|
||||
dev_info(sm501_gpio_to_dev(smchip->ourgpio)->dev,
|
||||
"changing mode of gpio, bit %08lx\n", bit);
|
||||
|
||||
ctrl = readl(smchip->control);
|
||||
ctrl &= ~bit;
|
||||
writel(ctrl, smchip->control);
|
||||
|
||||
sm501_sync_regs(sm501_gpio_to_dev(smchip->ourgpio));
|
||||
}
|
||||
}
|
||||
|
||||
static void sm501_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
|
||||
{
|
||||
|
@ -929,6 +949,8 @@ static void sm501_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
|||
writel(val, regs);
|
||||
|
||||
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
||||
sm501_gpio_ensure_gpio(smchip, bit);
|
||||
|
||||
spin_unlock_irqrestore(&smgpio->lock, save);
|
||||
}
|
||||
|
||||
|
@ -941,8 +963,8 @@ static int sm501_gpio_input(struct gpio_chip *chip, unsigned offset)
|
|||
unsigned long save;
|
||||
unsigned long ddr;
|
||||
|
||||
dev_info(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n",
|
||||
__func__, chip, offset);
|
||||
dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n",
|
||||
__func__, chip, offset);
|
||||
|
||||
spin_lock_irqsave(&smgpio->lock, save);
|
||||
|
||||
|
@ -950,6 +972,8 @@ static int sm501_gpio_input(struct gpio_chip *chip, unsigned offset)
|
|||
writel(ddr & ~bit, regs + SM501_GPIO_DDR_LOW);
|
||||
|
||||
sm501_sync_regs(sm501_gpio_to_dev(smgpio));
|
||||
sm501_gpio_ensure_gpio(smchip, bit);
|
||||
|
||||
spin_unlock_irqrestore(&smgpio->lock, save);
|
||||
|
||||
return 0;
|
||||
|
@ -1012,9 +1036,11 @@ static int __devinit sm501_gpio_register_chip(struct sm501_devdata *sm,
|
|||
if (base > 0)
|
||||
base += 32;
|
||||
chip->regbase = gpio->regs + SM501_GPIO_DATA_HIGH;
|
||||
chip->control = sm->regs + SM501_GPIO63_32_CONTROL;
|
||||
gchip->label = "SM501-HIGH";
|
||||
} else {
|
||||
chip->regbase = gpio->regs + SM501_GPIO_DATA_LOW;
|
||||
chip->control = sm->regs + SM501_GPIO31_0_CONTROL;
|
||||
gchip->label = "SM501-LOW";
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/i2c/twl4030.h>
|
||||
|
||||
#ifdef CONFIG_ARM
|
||||
#include <mach/cpu.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The TWL4030 "Triton 2" is one of a family of a multi-function "Power
|
||||
|
|
|
@ -82,4 +82,10 @@ config BATTERY_DA9030
|
|||
Say Y here to enable support for batteries charger integrated into
|
||||
DA9030 PMIC.
|
||||
|
||||
config CHARGER_PCF50633
|
||||
tristate "NXP PCF50633 MBC"
|
||||
depends on MFD_PCF50633
|
||||
help
|
||||
Say Y to include support for NXP PCF50633 Main Battery Charger.
|
||||
|
||||
endif # POWER_SUPPLY
|
||||
|
|
|
@ -25,3 +25,4 @@ obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
|
|||
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
|
||||
obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
|
||||
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
|
||||
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
|
|
@ -0,0 +1,358 @@
|
|||
/* NXP PCF50633 Main Battery Charger Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte, Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/mfd/pcf50633/mbc.h>
|
||||
|
||||
struct pcf50633_mbc {
|
||||
struct pcf50633 *pcf;
|
||||
|
||||
int adapter_active;
|
||||
int adapter_online;
|
||||
int usb_active;
|
||||
int usb_online;
|
||||
|
||||
struct power_supply usb;
|
||||
struct power_supply adapter;
|
||||
};
|
||||
|
||||
int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
|
||||
int ret = 0;
|
||||
u8 bits;
|
||||
|
||||
if (ma >= 1000)
|
||||
bits = PCF50633_MBCC7_USB_1000mA;
|
||||
else if (ma >= 500)
|
||||
bits = PCF50633_MBCC7_USB_500mA;
|
||||
else if (ma >= 100)
|
||||
bits = PCF50633_MBCC7_USB_100mA;
|
||||
else
|
||||
bits = PCF50633_MBCC7_USB_SUSPEND;
|
||||
|
||||
ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
|
||||
PCF50633_MBCC7_USB_MASK, bits);
|
||||
if (ret)
|
||||
dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
|
||||
else
|
||||
dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
|
||||
|
||||
power_supply_changed(&mbc->usb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
|
||||
|
||||
int pcf50633_mbc_get_status(struct pcf50633 *pcf)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
|
||||
int status = 0;
|
||||
|
||||
if (mbc->usb_online)
|
||||
status |= PCF50633_MBC_USB_ONLINE;
|
||||
if (mbc->usb_active)
|
||||
status |= PCF50633_MBC_USB_ACTIVE;
|
||||
if (mbc->adapter_online)
|
||||
status |= PCF50633_MBC_ADAPTER_ONLINE;
|
||||
if (mbc->adapter_active)
|
||||
status |= PCF50633_MBC_ADAPTER_ACTIVE;
|
||||
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status);
|
||||
|
||||
void pcf50633_mbc_set_status(struct pcf50633 *pcf, int what, int status)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
|
||||
|
||||
if (what & PCF50633_MBC_USB_ONLINE)
|
||||
mbc->usb_online = !!status;
|
||||
if (what & PCF50633_MBC_USB_ACTIVE)
|
||||
mbc->usb_active = !!status;
|
||||
if (what & PCF50633_MBC_ADAPTER_ONLINE)
|
||||
mbc->adapter_online = !!status;
|
||||
if (what & PCF50633_MBC_ADAPTER_ACTIVE)
|
||||
mbc->adapter_active = !!status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcf50633_mbc_set_status);
|
||||
|
||||
static ssize_t
|
||||
show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
|
||||
|
||||
u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
|
||||
u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
|
||||
|
||||
return sprintf(buf, "%d\n", chgmod);
|
||||
}
|
||||
static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
|
||||
|
||||
static ssize_t
|
||||
show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
|
||||
u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
|
||||
PCF50633_MBCC7_USB_MASK;
|
||||
unsigned int ma;
|
||||
|
||||
if (usblim == PCF50633_MBCC7_USB_1000mA)
|
||||
ma = 1000;
|
||||
else if (usblim == PCF50633_MBCC7_USB_500mA)
|
||||
ma = 500;
|
||||
else if (usblim == PCF50633_MBCC7_USB_100mA)
|
||||
ma = 100;
|
||||
else
|
||||
ma = 0;
|
||||
|
||||
return sprintf(buf, "%u\n", ma);
|
||||
}
|
||||
|
||||
static ssize_t set_usblim(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
|
||||
unsigned long ma;
|
||||
int ret;
|
||||
|
||||
ret = strict_strtoul(buf, 10, &ma);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
pcf50633_mbc_usb_curlim_set(mbc->pcf, ma);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
|
||||
|
||||
static struct attribute *pcf50633_mbc_sysfs_entries[] = {
|
||||
&dev_attr_chgmode.attr,
|
||||
&dev_attr_usb_curlim.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group mbc_attr_group = {
|
||||
.name = NULL, /* put in device directory */
|
||||
.attrs = pcf50633_mbc_sysfs_entries,
|
||||
};
|
||||
|
||||
static void
|
||||
pcf50633_mbc_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = data;
|
||||
|
||||
/* USB */
|
||||
if (irq == PCF50633_IRQ_USBINS) {
|
||||
mbc->usb_online = 1;
|
||||
} else if (irq == PCF50633_IRQ_USBREM) {
|
||||
mbc->usb_online = 0;
|
||||
mbc->usb_active = 0;
|
||||
pcf50633_mbc_usb_curlim_set(mbc->pcf, 0);
|
||||
}
|
||||
|
||||
/* Adapter */
|
||||
if (irq == PCF50633_IRQ_ADPINS) {
|
||||
mbc->adapter_online = 1;
|
||||
mbc->adapter_active = 1;
|
||||
} else if (irq == PCF50633_IRQ_ADPREM) {
|
||||
mbc->adapter_online = 0;
|
||||
mbc->adapter_active = 0;
|
||||
}
|
||||
|
||||
if (irq == PCF50633_IRQ_BATFULL) {
|
||||
mbc->usb_active = 0;
|
||||
mbc->adapter_active = 0;
|
||||
}
|
||||
|
||||
power_supply_changed(&mbc->usb);
|
||||
power_supply_changed(&mbc->adapter);
|
||||
|
||||
if (mbc->pcf->pdata->mbc_event_callback)
|
||||
mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq);
|
||||
}
|
||||
|
||||
static int adapter_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = mbc->adapter_online;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int usb_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = mbc->usb_online;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property power_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
};
|
||||
|
||||
static const u8 mbc_irq_handlers[] = {
|
||||
PCF50633_IRQ_ADPINS,
|
||||
PCF50633_IRQ_ADPREM,
|
||||
PCF50633_IRQ_USBINS,
|
||||
PCF50633_IRQ_USBREM,
|
||||
PCF50633_IRQ_BATFULL,
|
||||
PCF50633_IRQ_CHGHALT,
|
||||
PCF50633_IRQ_THLIMON,
|
||||
PCF50633_IRQ_THLIMOFF,
|
||||
PCF50633_IRQ_USBLIMON,
|
||||
PCF50633_IRQ_USBLIMOFF,
|
||||
PCF50633_IRQ_LOWSYS,
|
||||
PCF50633_IRQ_LOWBAT,
|
||||
};
|
||||
|
||||
static int __devinit pcf50633_mbc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_mbc *mbc;
|
||||
struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
int i;
|
||||
u8 mbcs1;
|
||||
|
||||
mbc = kzalloc(sizeof(*mbc), GFP_KERNEL);
|
||||
if (!mbc)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mbc);
|
||||
mbc->pcf = pdata->pcf;
|
||||
|
||||
/* Set up IRQ handlers */
|
||||
for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
|
||||
pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i],
|
||||
pcf50633_mbc_irq_handler, mbc);
|
||||
|
||||
/* Create power supplies */
|
||||
mbc->adapter.name = "adapter";
|
||||
mbc->adapter.type = POWER_SUPPLY_TYPE_MAINS;
|
||||
mbc->adapter.properties = power_props;
|
||||
mbc->adapter.num_properties = ARRAY_SIZE(power_props);
|
||||
mbc->adapter.get_property = &adapter_get_property;
|
||||
mbc->adapter.supplied_to = mbc->pcf->pdata->batteries;
|
||||
mbc->adapter.num_supplicants = mbc->pcf->pdata->num_batteries;
|
||||
|
||||
mbc->usb.name = "usb";
|
||||
mbc->usb.type = POWER_SUPPLY_TYPE_USB;
|
||||
mbc->usb.properties = power_props;
|
||||
mbc->usb.num_properties = ARRAY_SIZE(power_props);
|
||||
mbc->usb.get_property = usb_get_property;
|
||||
mbc->usb.supplied_to = mbc->pcf->pdata->batteries;
|
||||
mbc->usb.num_supplicants = mbc->pcf->pdata->num_batteries;
|
||||
|
||||
ret = power_supply_register(&pdev->dev, &mbc->adapter);
|
||||
if (ret) {
|
||||
dev_err(mbc->pcf->dev, "failed to register adapter\n");
|
||||
kfree(mbc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = power_supply_register(&pdev->dev, &mbc->usb);
|
||||
if (ret) {
|
||||
dev_err(mbc->pcf->dev, "failed to register usb\n");
|
||||
power_supply_unregister(&mbc->adapter);
|
||||
kfree(mbc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
|
||||
if (ret)
|
||||
dev_err(mbc->pcf->dev, "failed to create sysfs entries\n");
|
||||
|
||||
mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1);
|
||||
if (mbcs1 & PCF50633_MBCS1_USBPRES)
|
||||
pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc);
|
||||
if (mbcs1 & PCF50633_MBCS1_ADAPTPRES)
|
||||
pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_mbc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_mbc *mbc = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
/* Remove IRQ handlers */
|
||||
for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
|
||||
pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]);
|
||||
|
||||
power_supply_unregister(&mbc->usb);
|
||||
power_supply_unregister(&mbc->adapter);
|
||||
|
||||
kfree(mbc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pcf50633_mbc_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633-mbc",
|
||||
},
|
||||
.probe = pcf50633_mbc_probe,
|
||||
.remove = __devexit_p(pcf50633_mbc_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_mbc_init(void)
|
||||
{
|
||||
return platform_driver_register(&pcf50633_mbc_driver);
|
||||
}
|
||||
module_init(pcf50633_mbc_init);
|
||||
|
||||
static void __exit pcf50633_mbc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pcf50633_mbc_driver);
|
||||
}
|
||||
module_exit(pcf50633_mbc_exit);
|
||||
|
||||
MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
|
||||
MODULE_DESCRIPTION("PCF50633 mbc driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pcf50633-mbc");
|
|
@ -73,4 +73,11 @@ config REGULATOR_DA903X
|
|||
Say y here to support the BUCKs and LDOs regulators found on
|
||||
Dialog Semiconductor DA9030/DA9034 PMIC.
|
||||
|
||||
config REGULATOR_PCF50633
|
||||
tristate "PCF50633 regulator driver"
|
||||
depends on MFD_PCF50633
|
||||
help
|
||||
Say Y here to support the voltage regulators and convertors
|
||||
on PCF50633
|
||||
|
||||
endif
|
||||
|
|
|
@ -11,5 +11,6 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o
|
|||
obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
|
||||
obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
|
||||
|
||||
ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
/* NXP PCF50633 PMIC Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte and Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/mfd/pcf50633/pmic.h>
|
||||
|
||||
#define PCF50633_REGULATOR(_name, _id) \
|
||||
{ \
|
||||
.name = _name, \
|
||||
.id = _id, \
|
||||
.ops = &pcf50633_regulator_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.owner = THIS_MODULE, \
|
||||
}
|
||||
|
||||
static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = {
|
||||
[PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT,
|
||||
[PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT,
|
||||
[PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT,
|
||||
[PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT,
|
||||
[PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT,
|
||||
[PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT,
|
||||
[PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT,
|
||||
[PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT,
|
||||
[PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT,
|
||||
[PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT,
|
||||
[PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT,
|
||||
};
|
||||
|
||||
/* Bits from voltage value */
|
||||
static u8 auto_voltage_bits(unsigned int millivolts)
|
||||
{
|
||||
if (millivolts < 1800)
|
||||
return 0;
|
||||
if (millivolts > 3800)
|
||||
return 0xff;
|
||||
|
||||
millivolts -= 625;
|
||||
|
||||
return millivolts / 25;
|
||||
}
|
||||
|
||||
static u8 down_voltage_bits(unsigned int millivolts)
|
||||
{
|
||||
if (millivolts < 625)
|
||||
return 0;
|
||||
else if (millivolts > 3000)
|
||||
return 0xff;
|
||||
|
||||
millivolts -= 625;
|
||||
|
||||
return millivolts / 25;
|
||||
}
|
||||
|
||||
static u8 ldo_voltage_bits(unsigned int millivolts)
|
||||
{
|
||||
if (millivolts < 900)
|
||||
return 0;
|
||||
else if (millivolts > 3600)
|
||||
return 0x1f;
|
||||
|
||||
millivolts -= 900;
|
||||
return millivolts / 100;
|
||||
}
|
||||
|
||||
/* Obtain voltage value from bits */
|
||||
static unsigned int auto_voltage_value(u8 bits)
|
||||
{
|
||||
if (bits < 0x2f)
|
||||
return 0;
|
||||
|
||||
return 625 + (bits * 25);
|
||||
}
|
||||
|
||||
|
||||
static unsigned int down_voltage_value(u8 bits)
|
||||
{
|
||||
return 625 + (bits * 25);
|
||||
}
|
||||
|
||||
|
||||
static unsigned int ldo_voltage_value(u8 bits)
|
||||
{
|
||||
bits &= 0x1f;
|
||||
|
||||
return 900 + (bits * 100);
|
||||
}
|
||||
|
||||
static int pcf50633_regulator_set_voltage(struct regulator_dev *rdev,
|
||||
int min_uV, int max_uV)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
int regulator_id, millivolts;
|
||||
u8 volt_bits, regnr;
|
||||
|
||||
pcf = rdev_get_drvdata(rdev);
|
||||
|
||||
regulator_id = rdev_get_id(rdev);
|
||||
if (regulator_id >= PCF50633_NUM_REGULATORS)
|
||||
return -EINVAL;
|
||||
|
||||
millivolts = min_uV / 1000;
|
||||
|
||||
regnr = pcf50633_regulator_registers[regulator_id];
|
||||
|
||||
switch (regulator_id) {
|
||||
case PCF50633_REGULATOR_AUTO:
|
||||
volt_bits = auto_voltage_bits(millivolts);
|
||||
break;
|
||||
case PCF50633_REGULATOR_DOWN1:
|
||||
volt_bits = down_voltage_bits(millivolts);
|
||||
break;
|
||||
case PCF50633_REGULATOR_DOWN2:
|
||||
volt_bits = down_voltage_bits(millivolts);
|
||||
break;
|
||||
case PCF50633_REGULATOR_LDO1:
|
||||
case PCF50633_REGULATOR_LDO2:
|
||||
case PCF50633_REGULATOR_LDO3:
|
||||
case PCF50633_REGULATOR_LDO4:
|
||||
case PCF50633_REGULATOR_LDO5:
|
||||
case PCF50633_REGULATOR_LDO6:
|
||||
case PCF50633_REGULATOR_HCLDO:
|
||||
volt_bits = ldo_voltage_bits(millivolts);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return pcf50633_reg_write(pcf, regnr, volt_bits);
|
||||
}
|
||||
|
||||
static int pcf50633_regulator_get_voltage(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pcf50633 *pcf;
|
||||
int regulator_id, millivolts, volt_bits;
|
||||
u8 regnr;
|
||||
|
||||
pcf = rdev_get_drvdata(rdev);;
|
||||
|
||||
regulator_id = rdev_get_id(rdev);
|
||||
if (regulator_id >= PCF50633_NUM_REGULATORS)
|
||||
return -EINVAL;
|
||||
|
||||
regnr = pcf50633_regulator_registers[regulator_id];
|
||||
|
||||
volt_bits = pcf50633_reg_read(pcf, regnr);
|
||||
if (volt_bits < 0)
|
||||
return -1;
|
||||
|
||||
switch (regulator_id) {
|
||||
case PCF50633_REGULATOR_AUTO:
|
||||
millivolts = auto_voltage_value(volt_bits);
|
||||
break;
|
||||
case PCF50633_REGULATOR_DOWN1:
|
||||
millivolts = down_voltage_value(volt_bits);
|
||||
break;
|
||||
case PCF50633_REGULATOR_DOWN2:
|
||||
millivolts = down_voltage_value(volt_bits);
|
||||
break;
|
||||
case PCF50633_REGULATOR_LDO1:
|
||||
case PCF50633_REGULATOR_LDO2:
|
||||
case PCF50633_REGULATOR_LDO3:
|
||||
case PCF50633_REGULATOR_LDO4:
|
||||
case PCF50633_REGULATOR_LDO5:
|
||||
case PCF50633_REGULATOR_LDO6:
|
||||
case PCF50633_REGULATOR_HCLDO:
|
||||
millivolts = ldo_voltage_value(volt_bits);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return millivolts * 1000;
|
||||
}
|
||||
|
||||
static int pcf50633_regulator_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pcf50633 *pcf = rdev_get_drvdata(rdev);
|
||||
int regulator_id;
|
||||
u8 regnr;
|
||||
|
||||
regulator_id = rdev_get_id(rdev);
|
||||
if (regulator_id >= PCF50633_NUM_REGULATORS)
|
||||
return -EINVAL;
|
||||
|
||||
/* The *ENA register is always one after the *OUT register */
|
||||
regnr = pcf50633_regulator_registers[regulator_id] + 1;
|
||||
|
||||
return pcf50633_reg_set_bit_mask(pcf, regnr, PCF50633_REGULATOR_ON,
|
||||
PCF50633_REGULATOR_ON);
|
||||
}
|
||||
|
||||
static int pcf50633_regulator_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pcf50633 *pcf = rdev_get_drvdata(rdev);
|
||||
int regulator_id;
|
||||
u8 regnr;
|
||||
|
||||
regulator_id = rdev_get_id(rdev);
|
||||
if (regulator_id >= PCF50633_NUM_REGULATORS)
|
||||
return -EINVAL;
|
||||
|
||||
/* the *ENA register is always one after the *OUT register */
|
||||
regnr = pcf50633_regulator_registers[regulator_id] + 1;
|
||||
|
||||
return pcf50633_reg_set_bit_mask(pcf, regnr,
|
||||
PCF50633_REGULATOR_ON, 0);
|
||||
}
|
||||
|
||||
static int pcf50633_regulator_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct pcf50633 *pcf = rdev_get_drvdata(rdev);
|
||||
int regulator_id = rdev_get_id(rdev);
|
||||
u8 regnr;
|
||||
|
||||
regulator_id = rdev_get_id(rdev);
|
||||
if (regulator_id >= PCF50633_NUM_REGULATORS)
|
||||
return -EINVAL;
|
||||
|
||||
/* the *ENA register is always one after the *OUT register */
|
||||
regnr = pcf50633_regulator_registers[regulator_id] + 1;
|
||||
|
||||
return pcf50633_reg_read(pcf, regnr) & PCF50633_REGULATOR_ON;
|
||||
}
|
||||
|
||||
static struct regulator_ops pcf50633_regulator_ops = {
|
||||
.set_voltage = pcf50633_regulator_set_voltage,
|
||||
.get_voltage = pcf50633_regulator_get_voltage,
|
||||
.enable = pcf50633_regulator_enable,
|
||||
.disable = pcf50633_regulator_disable,
|
||||
.is_enabled = pcf50633_regulator_is_enabled,
|
||||
};
|
||||
|
||||
static struct regulator_desc regulators[] = {
|
||||
[PCF50633_REGULATOR_AUTO] =
|
||||
PCF50633_REGULATOR("auto", PCF50633_REGULATOR_AUTO),
|
||||
[PCF50633_REGULATOR_DOWN1] =
|
||||
PCF50633_REGULATOR("down1", PCF50633_REGULATOR_DOWN1),
|
||||
[PCF50633_REGULATOR_DOWN2] =
|
||||
PCF50633_REGULATOR("down2", PCF50633_REGULATOR_DOWN2),
|
||||
[PCF50633_REGULATOR_LDO1] =
|
||||
PCF50633_REGULATOR("ldo1", PCF50633_REGULATOR_LDO1),
|
||||
[PCF50633_REGULATOR_LDO2] =
|
||||
PCF50633_REGULATOR("ldo2", PCF50633_REGULATOR_LDO2),
|
||||
[PCF50633_REGULATOR_LDO3] =
|
||||
PCF50633_REGULATOR("ldo3", PCF50633_REGULATOR_LDO3),
|
||||
[PCF50633_REGULATOR_LDO4] =
|
||||
PCF50633_REGULATOR("ldo4", PCF50633_REGULATOR_LDO4),
|
||||
[PCF50633_REGULATOR_LDO5] =
|
||||
PCF50633_REGULATOR("ldo5", PCF50633_REGULATOR_LDO5),
|
||||
[PCF50633_REGULATOR_LDO6] =
|
||||
PCF50633_REGULATOR("ldo6", PCF50633_REGULATOR_LDO6),
|
||||
[PCF50633_REGULATOR_HCLDO] =
|
||||
PCF50633_REGULATOR("hcldo", PCF50633_REGULATOR_HCLDO),
|
||||
[PCF50633_REGULATOR_MEMLDO] =
|
||||
PCF50633_REGULATOR("memldo", PCF50633_REGULATOR_MEMLDO),
|
||||
};
|
||||
|
||||
static int __devinit pcf50633_regulator_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator_dev *rdev;
|
||||
struct pcf50633 *pcf;
|
||||
|
||||
/* Already set by core driver */
|
||||
pcf = platform_get_drvdata(pdev);
|
||||
|
||||
rdev = regulator_register(®ulators[pdev->id], &pdev->dev, pcf);
|
||||
if (IS_ERR(rdev))
|
||||
return PTR_ERR(rdev);
|
||||
|
||||
if (pcf->pdata->regulator_registered)
|
||||
pcf->pdata->regulator_registered(pcf, pdev->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_regulator_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator_dev *rdev = platform_get_drvdata(pdev);
|
||||
|
||||
regulator_unregister(rdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pcf50633_regulator_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633-regltr",
|
||||
},
|
||||
.probe = pcf50633_regulator_probe,
|
||||
.remove = __devexit_p(pcf50633_regulator_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_regulator_init(void)
|
||||
{
|
||||
return platform_driver_register(&pcf50633_regulator_driver);
|
||||
}
|
||||
module_init(pcf50633_regulator_init);
|
||||
|
||||
static void __exit pcf50633_regulator_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pcf50633_regulator_driver);
|
||||
}
|
||||
module_exit(pcf50633_regulator_exit);
|
||||
|
||||
MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
|
||||
MODULE_DESCRIPTION("PCF50633 regulator driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pcf50633-regulator");
|
|
@ -502,6 +502,13 @@ config RTC_DRV_WM8350
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called "rtc-wm8350".
|
||||
|
||||
config RTC_DRV_PCF50633
|
||||
depends on MFD_PCF50633
|
||||
tristate "NXP PCF50633 RTC"
|
||||
help
|
||||
If you say yes here you get support for the RTC subsystem of the
|
||||
NXP PCF50633 used in embedded systems.
|
||||
|
||||
comment "on-CPU RTC drivers"
|
||||
|
||||
config RTC_DRV_OMAP
|
||||
|
|
|
@ -74,3 +74,4 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
|
|||
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
|
||||
obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
|
||||
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
|
||||
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
|
||||
|
|
|
@ -0,0 +1,344 @@
|
|||
/* NXP PCF50633 RTC Driver
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* Author: Balaji Rao <balajirrao@openmoko.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Broken down from monstrous PCF50633 driver mainly by
|
||||
* Harald Welte, Andy Green and Werner Almesberger
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
|
||||
#define PCF50633_REG_RTCSC 0x59 /* Second */
|
||||
#define PCF50633_REG_RTCMN 0x5a /* Minute */
|
||||
#define PCF50633_REG_RTCHR 0x5b /* Hour */
|
||||
#define PCF50633_REG_RTCWD 0x5c /* Weekday */
|
||||
#define PCF50633_REG_RTCDT 0x5d /* Day */
|
||||
#define PCF50633_REG_RTCMT 0x5e /* Month */
|
||||
#define PCF50633_REG_RTCYR 0x5f /* Year */
|
||||
#define PCF50633_REG_RTCSCA 0x60 /* Alarm Second */
|
||||
#define PCF50633_REG_RTCMNA 0x61 /* Alarm Minute */
|
||||
#define PCF50633_REG_RTCHRA 0x62 /* Alarm Hour */
|
||||
#define PCF50633_REG_RTCWDA 0x63 /* Alarm Weekday */
|
||||
#define PCF50633_REG_RTCDTA 0x64 /* Alarm Day */
|
||||
#define PCF50633_REG_RTCMTA 0x65 /* Alarm Month */
|
||||
#define PCF50633_REG_RTCYRA 0x66 /* Alarm Year */
|
||||
|
||||
enum pcf50633_time_indexes {
|
||||
PCF50633_TI_SEC,
|
||||
PCF50633_TI_MIN,
|
||||
PCF50633_TI_HOUR,
|
||||
PCF50633_TI_WKDAY,
|
||||
PCF50633_TI_DAY,
|
||||
PCF50633_TI_MONTH,
|
||||
PCF50633_TI_YEAR,
|
||||
PCF50633_TI_EXTENT /* always last */
|
||||
};
|
||||
|
||||
struct pcf50633_time {
|
||||
u_int8_t time[PCF50633_TI_EXTENT];
|
||||
};
|
||||
|
||||
struct pcf50633_rtc {
|
||||
int alarm_enabled;
|
||||
int second_enabled;
|
||||
|
||||
struct pcf50633 *pcf;
|
||||
struct rtc_device *rtc_dev;
|
||||
};
|
||||
|
||||
static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50633_time *pcf)
|
||||
{
|
||||
rtc->tm_sec = bcd2bin(pcf->time[PCF50633_TI_SEC]);
|
||||
rtc->tm_min = bcd2bin(pcf->time[PCF50633_TI_MIN]);
|
||||
rtc->tm_hour = bcd2bin(pcf->time[PCF50633_TI_HOUR]);
|
||||
rtc->tm_wday = bcd2bin(pcf->time[PCF50633_TI_WKDAY]);
|
||||
rtc->tm_mday = bcd2bin(pcf->time[PCF50633_TI_DAY]);
|
||||
rtc->tm_mon = bcd2bin(pcf->time[PCF50633_TI_MONTH]);
|
||||
rtc->tm_year = bcd2bin(pcf->time[PCF50633_TI_YEAR]) + 100;
|
||||
}
|
||||
|
||||
static void rtc2pcf_time(struct pcf50633_time *pcf, struct rtc_time *rtc)
|
||||
{
|
||||
pcf->time[PCF50633_TI_SEC] = bin2bcd(rtc->tm_sec);
|
||||
pcf->time[PCF50633_TI_MIN] = bin2bcd(rtc->tm_min);
|
||||
pcf->time[PCF50633_TI_HOUR] = bin2bcd(rtc->tm_hour);
|
||||
pcf->time[PCF50633_TI_WKDAY] = bin2bcd(rtc->tm_wday);
|
||||
pcf->time[PCF50633_TI_DAY] = bin2bcd(rtc->tm_mday);
|
||||
pcf->time[PCF50633_TI_MONTH] = bin2bcd(rtc->tm_mon);
|
||||
pcf->time[PCF50633_TI_YEAR] = bin2bcd(rtc->tm_year % 100);
|
||||
}
|
||||
|
||||
static int
|
||||
pcf50633_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
||||
{
|
||||
struct pcf50633_rtc *rtc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (enabled)
|
||||
err = pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
else
|
||||
err = pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
rtc->alarm_enabled = enabled;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pcf50633_rtc_update_irq_enable(struct device *dev, unsigned int enabled)
|
||||
{
|
||||
struct pcf50633_rtc *rtc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (enabled)
|
||||
err = pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
else
|
||||
err = pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
rtc->second_enabled = enabled;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcf50633_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct pcf50633_rtc *rtc;
|
||||
struct pcf50633_time pcf_tm;
|
||||
int ret;
|
||||
|
||||
rtc = dev_get_drvdata(dev);
|
||||
|
||||
ret = pcf50633_read_block(rtc->pcf, PCF50633_REG_RTCSC,
|
||||
PCF50633_TI_EXTENT,
|
||||
&pcf_tm.time[0]);
|
||||
if (ret != PCF50633_TI_EXTENT) {
|
||||
dev_err(dev, "Failed to read time\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
|
||||
pcf_tm.time[PCF50633_TI_DAY],
|
||||
pcf_tm.time[PCF50633_TI_MONTH],
|
||||
pcf_tm.time[PCF50633_TI_YEAR],
|
||||
pcf_tm.time[PCF50633_TI_HOUR],
|
||||
pcf_tm.time[PCF50633_TI_MIN],
|
||||
pcf_tm.time[PCF50633_TI_SEC]);
|
||||
|
||||
pcf2rtc_time(tm, &pcf_tm);
|
||||
|
||||
dev_dbg(dev, "RTC_TIME: %u.%u.%u %u:%u:%u\n",
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
return rtc_valid_tm(tm);
|
||||
}
|
||||
|
||||
static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct pcf50633_rtc *rtc;
|
||||
struct pcf50633_time pcf_tm;
|
||||
int second_masked, alarm_masked, ret = 0;
|
||||
|
||||
rtc = dev_get_drvdata(dev);
|
||||
|
||||
dev_dbg(dev, "RTC_TIME: %u.%u.%u %u:%u:%u\n",
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
rtc2pcf_time(&pcf_tm, tm);
|
||||
|
||||
dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
|
||||
pcf_tm.time[PCF50633_TI_DAY],
|
||||
pcf_tm.time[PCF50633_TI_MONTH],
|
||||
pcf_tm.time[PCF50633_TI_YEAR],
|
||||
pcf_tm.time[PCF50633_TI_HOUR],
|
||||
pcf_tm.time[PCF50633_TI_MIN],
|
||||
pcf_tm.time[PCF50633_TI_SEC]);
|
||||
|
||||
|
||||
second_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
alarm_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
if (!second_masked)
|
||||
pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
if (!alarm_masked)
|
||||
pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
/* Returns 0 on success */
|
||||
ret = pcf50633_write_block(rtc->pcf, PCF50633_REG_RTCSC,
|
||||
PCF50633_TI_EXTENT,
|
||||
&pcf_tm.time[0]);
|
||||
|
||||
if (!second_masked)
|
||||
pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
if (!alarm_masked)
|
||||
pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct pcf50633_rtc *rtc;
|
||||
struct pcf50633_time pcf_tm;
|
||||
int ret = 0;
|
||||
|
||||
rtc = dev_get_drvdata(dev);
|
||||
|
||||
alrm->enabled = rtc->alarm_enabled;
|
||||
|
||||
ret = pcf50633_read_block(rtc->pcf, PCF50633_REG_RTCSCA,
|
||||
PCF50633_TI_EXTENT, &pcf_tm.time[0]);
|
||||
if (ret != PCF50633_TI_EXTENT) {
|
||||
dev_err(dev, "Failed to read time\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
pcf2rtc_time(&alrm->time, &pcf_tm);
|
||||
|
||||
return rtc_valid_tm(&alrm->time);
|
||||
}
|
||||
|
||||
static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct pcf50633_rtc *rtc;
|
||||
struct pcf50633_time pcf_tm;
|
||||
int alarm_masked, ret = 0;
|
||||
|
||||
rtc = dev_get_drvdata(dev);
|
||||
|
||||
rtc2pcf_time(&pcf_tm, &alrm->time);
|
||||
|
||||
/* do like mktime does and ignore tm_wday */
|
||||
pcf_tm.time[PCF50633_TI_WKDAY] = 7;
|
||||
|
||||
alarm_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
/* disable alarm interrupt */
|
||||
if (!alarm_masked)
|
||||
pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
/* Returns 0 on success */
|
||||
ret = pcf50633_write_block(rtc->pcf, PCF50633_REG_RTCSCA,
|
||||
PCF50633_TI_EXTENT, &pcf_tm.time[0]);
|
||||
|
||||
if (!alarm_masked)
|
||||
pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct rtc_class_ops pcf50633_rtc_ops = {
|
||||
.read_time = pcf50633_rtc_read_time,
|
||||
.set_time = pcf50633_rtc_set_time,
|
||||
.read_alarm = pcf50633_rtc_read_alarm,
|
||||
.set_alarm = pcf50633_rtc_set_alarm,
|
||||
.alarm_irq_enable = pcf50633_rtc_alarm_irq_enable,
|
||||
.update_irq_enable = pcf50633_rtc_update_irq_enable,
|
||||
};
|
||||
|
||||
static void pcf50633_rtc_irq(int irq, void *data)
|
||||
{
|
||||
struct pcf50633_rtc *rtc = data;
|
||||
|
||||
switch (irq) {
|
||||
case PCF50633_IRQ_ALARM:
|
||||
rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF);
|
||||
break;
|
||||
case PCF50633_IRQ_SECOND:
|
||||
rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int __devinit pcf50633_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_subdev_pdata *pdata;
|
||||
struct pcf50633_rtc *rtc;
|
||||
|
||||
|
||||
rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
|
||||
if (!rtc)
|
||||
return -ENOMEM;
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
rtc->pcf = pdata->pcf;
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
rtc->rtc_dev = rtc_device_register("pcf50633-rtc", &pdev->dev,
|
||||
&pcf50633_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc->rtc_dev)) {
|
||||
kfree(rtc);
|
||||
return PTR_ERR(rtc->rtc_dev);
|
||||
}
|
||||
|
||||
pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_ALARM,
|
||||
pcf50633_rtc_irq, rtc);
|
||||
pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_SECOND,
|
||||
pcf50633_rtc_irq, rtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit pcf50633_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pcf50633_rtc *rtc;
|
||||
|
||||
rtc = platform_get_drvdata(pdev);
|
||||
|
||||
pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_ALARM);
|
||||
pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_SECOND);
|
||||
|
||||
rtc_device_unregister(rtc->rtc_dev);
|
||||
kfree(rtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pcf50633_rtc_driver = {
|
||||
.driver = {
|
||||
.name = "pcf50633-rtc",
|
||||
},
|
||||
.probe = pcf50633_rtc_probe,
|
||||
.remove = __devexit_p(pcf50633_rtc_remove),
|
||||
};
|
||||
|
||||
static int __init pcf50633_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&pcf50633_rtc_driver);
|
||||
}
|
||||
module_init(pcf50633_rtc_init);
|
||||
|
||||
static void __exit pcf50633_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pcf50633_rtc_driver);
|
||||
}
|
||||
module_exit(pcf50633_rtc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCF50633 RTC driver");
|
||||
MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* adc.h -- Driver for NXP PCF50633 ADC
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 __LINUX_MFD_PCF50633_ADC_H
|
||||
#define __LINUX_MFD_PCF50633_ADC_H
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
/* ADC Registers */
|
||||
#define PCF50633_REG_ADCC3 0x52
|
||||
#define PCF50633_REG_ADCC2 0x53
|
||||
#define PCF50633_REG_ADCC1 0x54
|
||||
#define PCF50633_REG_ADCS1 0x55
|
||||
#define PCF50633_REG_ADCS2 0x56
|
||||
#define PCF50633_REG_ADCS3 0x57
|
||||
|
||||
#define PCF50633_ADCC1_ADCSTART 0x01
|
||||
#define PCF50633_ADCC1_RES_10BIT 0x02
|
||||
#define PCF50633_ADCC1_AVERAGE_NO 0x00
|
||||
#define PCF50633_ADCC1_AVERAGE_4 0x04
|
||||
#define PCF50633_ADCC1_AVERAGE_8 0x08
|
||||
#define PCF50633_ADCC1_AVERAGE_16 0x0c
|
||||
#define PCF50633_ADCC1_MUX_BATSNS_RES 0x00
|
||||
#define PCF50633_ADCC1_MUX_BATSNS_SUBTR 0x10
|
||||
#define PCF50633_ADCC1_MUX_ADCIN2_RES 0x20
|
||||
#define PCF50633_ADCC1_MUX_ADCIN2_SUBTR 0x30
|
||||
#define PCF50633_ADCC1_MUX_BATTEMP 0x60
|
||||
#define PCF50633_ADCC1_MUX_ADCIN1 0x70
|
||||
#define PCF50633_ADCC1_AVERAGE_MASK 0x0c
|
||||
#define PCF50633_ADCC1_ADCMUX_MASK 0xf0
|
||||
|
||||
#define PCF50633_ADCC2_RATIO_NONE 0x00
|
||||
#define PCF50633_ADCC2_RATIO_BATTEMP 0x01
|
||||
#define PCF50633_ADCC2_RATIO_ADCIN1 0x02
|
||||
#define PCF50633_ADCC2_RATIO_BOTH 0x03
|
||||
#define PCF50633_ADCC2_RATIOSETTL_100US 0x04
|
||||
|
||||
#define PCF50633_ADCC3_ACCSW_EN 0x01
|
||||
#define PCF50633_ADCC3_NTCSW_EN 0x04
|
||||
#define PCF50633_ADCC3_RES_DIV_TWO 0x10
|
||||
#define PCF50633_ADCC3_RES_DIV_THREE 0x00
|
||||
|
||||
#define PCF50633_ADCS3_REF_NTCSW 0x00
|
||||
#define PCF50633_ADCS3_REF_ACCSW 0x10
|
||||
#define PCF50633_ADCS3_REF_2V0 0x20
|
||||
#define PCF50633_ADCS3_REF_VISA 0x30
|
||||
#define PCF50633_ADCS3_REF_2V0_2 0x70
|
||||
#define PCF50633_ADCS3_ADCRDY 0x80
|
||||
|
||||
#define PCF50633_ADCS3_ADCDAT1L_MASK 0x03
|
||||
#define PCF50633_ADCS3_ADCDAT2L_MASK 0x0c
|
||||
#define PCF50633_ADCS3_ADCDAT2L_SHIFT 2
|
||||
#define PCF50633_ASCS3_REF_MASK 0x70
|
||||
|
||||
extern int
|
||||
pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
|
||||
void (*callback)(struct pcf50633 *, void *, int),
|
||||
void *callback_param);
|
||||
extern int
|
||||
pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg);
|
||||
|
||||
#endif /* __LINUX_PCF50633_ADC_H */
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* core.h -- Core driver for NXP PCF50633
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 __LINUX_MFD_PCF50633_CORE_H
|
||||
#define __LINUX_MFD_PCF50633_CORE_H
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
struct pcf50633;
|
||||
|
||||
#define PCF50633_NUM_REGULATORS 11
|
||||
|
||||
struct pcf50633_platform_data {
|
||||
struct regulator_init_data reg_init_data[PCF50633_NUM_REGULATORS];
|
||||
|
||||
char **batteries;
|
||||
int num_batteries;
|
||||
|
||||
/* Callbacks */
|
||||
void (*probe_done)(struct pcf50633 *);
|
||||
void (*mbc_event_callback)(struct pcf50633 *, int);
|
||||
void (*regulator_registered)(struct pcf50633 *, int);
|
||||
void (*force_shutdown)(struct pcf50633 *);
|
||||
|
||||
u8 resumers[5];
|
||||
};
|
||||
|
||||
struct pcf50633_subdev_pdata {
|
||||
struct pcf50633 *pcf;
|
||||
};
|
||||
|
||||
struct pcf50633_irq {
|
||||
void (*handler) (int, void *);
|
||||
void *data;
|
||||
};
|
||||
|
||||
int pcf50633_register_irq(struct pcf50633 *pcf, int irq,
|
||||
void (*handler) (int, void *), void *data);
|
||||
int pcf50633_free_irq(struct pcf50633 *pcf, int irq);
|
||||
|
||||
int pcf50633_irq_mask(struct pcf50633 *pcf, int irq);
|
||||
int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq);
|
||||
int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq);
|
||||
|
||||
int pcf50633_read_block(struct pcf50633 *, u8 reg,
|
||||
int nr_regs, u8 *data);
|
||||
int pcf50633_write_block(struct pcf50633 *pcf, u8 reg,
|
||||
int nr_regs, u8 *data);
|
||||
u8 pcf50633_reg_read(struct pcf50633 *, u8 reg);
|
||||
int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val);
|
||||
|
||||
int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val);
|
||||
int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 bits);
|
||||
|
||||
/* Interrupt registers */
|
||||
|
||||
#define PCF50633_REG_INT1 0x02
|
||||
#define PCF50633_REG_INT2 0x03
|
||||
#define PCF50633_REG_INT3 0x04
|
||||
#define PCF50633_REG_INT4 0x05
|
||||
#define PCF50633_REG_INT5 0x06
|
||||
|
||||
#define PCF50633_REG_INT1M 0x07
|
||||
#define PCF50633_REG_INT2M 0x08
|
||||
#define PCF50633_REG_INT3M 0x09
|
||||
#define PCF50633_REG_INT4M 0x0a
|
||||
#define PCF50633_REG_INT5M 0x0b
|
||||
|
||||
enum {
|
||||
/* Chip IRQs */
|
||||
PCF50633_IRQ_ADPINS,
|
||||
PCF50633_IRQ_ADPREM,
|
||||
PCF50633_IRQ_USBINS,
|
||||
PCF50633_IRQ_USBREM,
|
||||
PCF50633_IRQ_RESERVED1,
|
||||
PCF50633_IRQ_RESERVED2,
|
||||
PCF50633_IRQ_ALARM,
|
||||
PCF50633_IRQ_SECOND,
|
||||
PCF50633_IRQ_ONKEYR,
|
||||
PCF50633_IRQ_ONKEYF,
|
||||
PCF50633_IRQ_EXTON1R,
|
||||
PCF50633_IRQ_EXTON1F,
|
||||
PCF50633_IRQ_EXTON2R,
|
||||
PCF50633_IRQ_EXTON2F,
|
||||
PCF50633_IRQ_EXTON3R,
|
||||
PCF50633_IRQ_EXTON3F,
|
||||
PCF50633_IRQ_BATFULL,
|
||||
PCF50633_IRQ_CHGHALT,
|
||||
PCF50633_IRQ_THLIMON,
|
||||
PCF50633_IRQ_THLIMOFF,
|
||||
PCF50633_IRQ_USBLIMON,
|
||||
PCF50633_IRQ_USBLIMOFF,
|
||||
PCF50633_IRQ_ADCRDY,
|
||||
PCF50633_IRQ_ONKEY1S,
|
||||
PCF50633_IRQ_LOWSYS,
|
||||
PCF50633_IRQ_LOWBAT,
|
||||
PCF50633_IRQ_HIGHTMP,
|
||||
PCF50633_IRQ_AUTOPWRFAIL,
|
||||
PCF50633_IRQ_DWN1PWRFAIL,
|
||||
PCF50633_IRQ_DWN2PWRFAIL,
|
||||
PCF50633_IRQ_LEDPWRFAIL,
|
||||
PCF50633_IRQ_LEDOVP,
|
||||
PCF50633_IRQ_LDO1PWRFAIL,
|
||||
PCF50633_IRQ_LDO2PWRFAIL,
|
||||
PCF50633_IRQ_LDO3PWRFAIL,
|
||||
PCF50633_IRQ_LDO4PWRFAIL,
|
||||
PCF50633_IRQ_LDO5PWRFAIL,
|
||||
PCF50633_IRQ_LDO6PWRFAIL,
|
||||
PCF50633_IRQ_HCLDOPWRFAIL,
|
||||
PCF50633_IRQ_HCLDOOVL,
|
||||
|
||||
/* Always last */
|
||||
PCF50633_NUM_IRQ,
|
||||
};
|
||||
|
||||
struct pcf50633 {
|
||||
struct device *dev;
|
||||
struct i2c_client *i2c_client;
|
||||
|
||||
struct pcf50633_platform_data *pdata;
|
||||
int irq;
|
||||
struct pcf50633_irq irq_handler[PCF50633_NUM_IRQ];
|
||||
struct work_struct irq_work;
|
||||
struct mutex lock;
|
||||
|
||||
u8 mask_regs[5];
|
||||
|
||||
u8 suspend_irq_masks[5];
|
||||
u8 resume_reason[5];
|
||||
int is_suspended;
|
||||
|
||||
int onkey1s_held;
|
||||
|
||||
struct platform_device *rtc_pdev;
|
||||
struct platform_device *mbc_pdev;
|
||||
struct platform_device *adc_pdev;
|
||||
struct platform_device *input_pdev;
|
||||
struct platform_device *regulator_pdev[PCF50633_NUM_REGULATORS];
|
||||
};
|
||||
|
||||
enum pcf50633_reg_int1 {
|
||||
PCF50633_INT1_ADPINS = 0x01, /* Adapter inserted */
|
||||
PCF50633_INT1_ADPREM = 0x02, /* Adapter removed */
|
||||
PCF50633_INT1_USBINS = 0x04, /* USB inserted */
|
||||
PCF50633_INT1_USBREM = 0x08, /* USB removed */
|
||||
/* reserved */
|
||||
PCF50633_INT1_ALARM = 0x40, /* RTC alarm time is reached */
|
||||
PCF50633_INT1_SECOND = 0x80, /* RTC periodic second interrupt */
|
||||
};
|
||||
|
||||
enum pcf50633_reg_int2 {
|
||||
PCF50633_INT2_ONKEYR = 0x01, /* ONKEY rising edge */
|
||||
PCF50633_INT2_ONKEYF = 0x02, /* ONKEY falling edge */
|
||||
PCF50633_INT2_EXTON1R = 0x04, /* EXTON1 rising edge */
|
||||
PCF50633_INT2_EXTON1F = 0x08, /* EXTON1 falling edge */
|
||||
PCF50633_INT2_EXTON2R = 0x10, /* EXTON2 rising edge */
|
||||
PCF50633_INT2_EXTON2F = 0x20, /* EXTON2 falling edge */
|
||||
PCF50633_INT2_EXTON3R = 0x40, /* EXTON3 rising edge */
|
||||
PCF50633_INT2_EXTON3F = 0x80, /* EXTON3 falling edge */
|
||||
};
|
||||
|
||||
enum pcf50633_reg_int3 {
|
||||
PCF50633_INT3_BATFULL = 0x01, /* Battery full */
|
||||
PCF50633_INT3_CHGHALT = 0x02, /* Charger halt */
|
||||
PCF50633_INT3_THLIMON = 0x04,
|
||||
PCF50633_INT3_THLIMOFF = 0x08,
|
||||
PCF50633_INT3_USBLIMON = 0x10,
|
||||
PCF50633_INT3_USBLIMOFF = 0x20,
|
||||
PCF50633_INT3_ADCRDY = 0x40, /* ADC result ready */
|
||||
PCF50633_INT3_ONKEY1S = 0x80, /* ONKEY pressed 1 second */
|
||||
};
|
||||
|
||||
enum pcf50633_reg_int4 {
|
||||
PCF50633_INT4_LOWSYS = 0x01,
|
||||
PCF50633_INT4_LOWBAT = 0x02,
|
||||
PCF50633_INT4_HIGHTMP = 0x04,
|
||||
PCF50633_INT4_AUTOPWRFAIL = 0x08,
|
||||
PCF50633_INT4_DWN1PWRFAIL = 0x10,
|
||||
PCF50633_INT4_DWN2PWRFAIL = 0x20,
|
||||
PCF50633_INT4_LEDPWRFAIL = 0x40,
|
||||
PCF50633_INT4_LEDOVP = 0x80,
|
||||
};
|
||||
|
||||
enum pcf50633_reg_int5 {
|
||||
PCF50633_INT5_LDO1PWRFAIL = 0x01,
|
||||
PCF50633_INT5_LDO2PWRFAIL = 0x02,
|
||||
PCF50633_INT5_LDO3PWRFAIL = 0x04,
|
||||
PCF50633_INT5_LDO4PWRFAIL = 0x08,
|
||||
PCF50633_INT5_LDO5PWRFAIL = 0x10,
|
||||
PCF50633_INT5_LDO6PWRFAIL = 0x20,
|
||||
PCF50633_INT5_HCLDOPWRFAIL = 0x40,
|
||||
PCF50633_INT5_HCLDOOVL = 0x80,
|
||||
};
|
||||
|
||||
/* misc. registers */
|
||||
#define PCF50633_REG_OOCSHDWN 0x0c
|
||||
|
||||
/* LED registers */
|
||||
#define PCF50633_REG_LEDOUT 0x28
|
||||
#define PCF50633_REG_LEDENA 0x29
|
||||
#define PCF50633_REG_LEDCTL 0x2a
|
||||
#define PCF50633_REG_LEDDIM 0x2b
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* gpio.h -- GPIO driver for NXP PCF50633
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 __LINUX_MFD_PCF50633_GPIO_H
|
||||
#define __LINUX_MFD_PCF50633_GPIO_H
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
|
||||
#define PCF50633_GPIO1 1
|
||||
#define PCF50633_GPIO2 2
|
||||
#define PCF50633_GPIO3 3
|
||||
#define PCF50633_GPO 4
|
||||
|
||||
#define PCF50633_REG_GPIO1CFG 0x14
|
||||
#define PCF50633_REG_GPIO2CFG 0x15
|
||||
#define PCF50633_REG_GPIO3CFG 0x16
|
||||
#define PCF50633_REG_GPOCFG 0x17
|
||||
|
||||
#define PCF50633_GPOCFG_GPOSEL_MASK 0x07
|
||||
|
||||
enum pcf50633_reg_gpocfg {
|
||||
PCF50633_GPOCFG_GPOSEL_0 = 0x00,
|
||||
PCF50633_GPOCFG_GPOSEL_LED_NFET = 0x01,
|
||||
PCF50633_GPOCFG_GPOSEL_SYSxOK = 0x02,
|
||||
PCF50633_GPOCFG_GPOSEL_CLK32K = 0x03,
|
||||
PCF50633_GPOCFG_GPOSEL_ADAPUSB = 0x04,
|
||||
PCF50633_GPOCFG_GPOSEL_USBxOK = 0x05,
|
||||
PCF50633_GPOCFG_GPOSEL_ACTPH4 = 0x06,
|
||||
PCF50633_GPOCFG_GPOSEL_1 = 0x07,
|
||||
PCF50633_GPOCFG_GPOSEL_INVERSE = 0x08,
|
||||
};
|
||||
|
||||
int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val);
|
||||
u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio);
|
||||
|
||||
int pcf50633_gpio_invert_set(struct pcf50633 *, int gpio, int invert);
|
||||
int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio);
|
||||
|
||||
int pcf50633_gpio_power_supply_set(struct pcf50633 *,
|
||||
int gpio, int regulator, int on);
|
||||
#endif /* __LINUX_MFD_PCF50633_GPIO_H */
|
||||
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* mbc.h -- Driver for NXP PCF50633 Main Battery Charger
|
||||
*
|
||||
* (C) 2006-2008 by Openmoko, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 __LINUX_MFD_PCF50633_MBC_H
|
||||
#define __LINUX_MFD_PCF50633_MBC_H
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define PCF50633_REG_MBCC1 0x43
|
||||
#define PCF50633_REG_MBCC2 0x44
|
||||
#define PCF50633_REG_MBCC3 0x45
|
||||
#define PCF50633_REG_MBCC4 0x46
|
||||
#define PCF50633_REG_MBCC5 0x47
|
||||
#define PCF50633_REG_MBCC6 0x48
|
||||
#define PCF50633_REG_MBCC7 0x49
|
||||
#define PCF50633_REG_MBCC8 0x4a
|
||||
#define PCF50633_REG_MBCS1 0x4b
|
||||
#define PCF50633_REG_MBCS2 0x4c
|
||||
#define PCF50633_REG_MBCS3 0x4d
|
||||
|
||||
enum pcf50633_reg_mbcc1 {
|
||||
PCF50633_MBCC1_CHGENA = 0x01, /* Charger enable */
|
||||
PCF50633_MBCC1_AUTOSTOP = 0x02,
|
||||
PCF50633_MBCC1_AUTORES = 0x04, /* automatic resume */
|
||||
PCF50633_MBCC1_RESUME = 0x08, /* explicit resume cmd */
|
||||
PCF50633_MBCC1_RESTART = 0x10, /* restart charging */
|
||||
PCF50633_MBCC1_PREWDTIME_60M = 0x20, /* max. precharging time */
|
||||
PCF50633_MBCC1_WDTIME_1H = 0x00,
|
||||
PCF50633_MBCC1_WDTIME_2H = 0x40,
|
||||
PCF50633_MBCC1_WDTIME_4H = 0x80,
|
||||
PCF50633_MBCC1_WDTIME_6H = 0xc0,
|
||||
};
|
||||
#define PCF50633_MBCC1_WDTIME_MASK 0xc0
|
||||
|
||||
enum pcf50633_reg_mbcc2 {
|
||||
PCF50633_MBCC2_VBATCOND_2V7 = 0x00,
|
||||
PCF50633_MBCC2_VBATCOND_2V85 = 0x01,
|
||||
PCF50633_MBCC2_VBATCOND_3V0 = 0x02,
|
||||
PCF50633_MBCC2_VBATCOND_3V15 = 0x03,
|
||||
PCF50633_MBCC2_VMAX_4V = 0x00,
|
||||
PCF50633_MBCC2_VMAX_4V20 = 0x28,
|
||||
PCF50633_MBCC2_VRESDEBTIME_64S = 0x80, /* debounce time (32/64sec) */
|
||||
};
|
||||
|
||||
enum pcf50633_reg_mbcc7 {
|
||||
PCF50633_MBCC7_USB_100mA = 0x00,
|
||||
PCF50633_MBCC7_USB_500mA = 0x01,
|
||||
PCF50633_MBCC7_USB_1000mA = 0x02,
|
||||
PCF50633_MBCC7_USB_SUSPEND = 0x03,
|
||||
PCF50633_MBCC7_BATTEMP_EN = 0x04,
|
||||
PCF50633_MBCC7_BATSYSIMAX_1A6 = 0x00,
|
||||
PCF50633_MBCC7_BATSYSIMAX_1A8 = 0x40,
|
||||
PCF50633_MBCC7_BATSYSIMAX_2A0 = 0x80,
|
||||
PCF50633_MBCC7_BATSYSIMAX_2A2 = 0xc0,
|
||||
};
|
||||
#define PCF50633_MBCC7_USB_MASK 0x03
|
||||
|
||||
enum pcf50633_reg_mbcc8 {
|
||||
PCF50633_MBCC8_USBENASUS = 0x10,
|
||||
};
|
||||
|
||||
enum pcf50633_reg_mbcs1 {
|
||||
PCF50633_MBCS1_USBPRES = 0x01,
|
||||
PCF50633_MBCS1_USBOK = 0x02,
|
||||
PCF50633_MBCS1_ADAPTPRES = 0x04,
|
||||
PCF50633_MBCS1_ADAPTOK = 0x08,
|
||||
PCF50633_MBCS1_TBAT_OK = 0x00,
|
||||
PCF50633_MBCS1_TBAT_ABOVE = 0x10,
|
||||
PCF50633_MBCS1_TBAT_BELOW = 0x20,
|
||||
PCF50633_MBCS1_TBAT_UNDEF = 0x30,
|
||||
PCF50633_MBCS1_PREWDTEXP = 0x40,
|
||||
PCF50633_MBCS1_WDTEXP = 0x80,
|
||||
};
|
||||
|
||||
enum pcf50633_reg_mbcs2_mbcmod {
|
||||
PCF50633_MBCS2_MBC_PLAY = 0x00,
|
||||
PCF50633_MBCS2_MBC_USB_PRE = 0x01,
|
||||
PCF50633_MBCS2_MBC_USB_PRE_WAIT = 0x02,
|
||||
PCF50633_MBCS2_MBC_USB_FAST = 0x03,
|
||||
PCF50633_MBCS2_MBC_USB_FAST_WAIT = 0x04,
|
||||
PCF50633_MBCS2_MBC_USB_SUSPEND = 0x05,
|
||||
PCF50633_MBCS2_MBC_ADP_PRE = 0x06,
|
||||
PCF50633_MBCS2_MBC_ADP_PRE_WAIT = 0x07,
|
||||
PCF50633_MBCS2_MBC_ADP_FAST = 0x08,
|
||||
PCF50633_MBCS2_MBC_ADP_FAST_WAIT = 0x09,
|
||||
PCF50633_MBCS2_MBC_BAT_FULL = 0x0a,
|
||||
PCF50633_MBCS2_MBC_HALT = 0x0b,
|
||||
};
|
||||
#define PCF50633_MBCS2_MBC_MASK 0x0f
|
||||
enum pcf50633_reg_mbcs2_chgstat {
|
||||
PCF50633_MBCS2_CHGS_NONE = 0x00,
|
||||
PCF50633_MBCS2_CHGS_ADAPTER = 0x10,
|
||||
PCF50633_MBCS2_CHGS_USB = 0x20,
|
||||
PCF50633_MBCS2_CHGS_BOTH = 0x30,
|
||||
};
|
||||
#define PCF50633_MBCS2_RESSTAT_AUTO 0x40
|
||||
|
||||
enum pcf50633_reg_mbcs3 {
|
||||
PCF50633_MBCS3_USBLIM_PLAY = 0x01,
|
||||
PCF50633_MBCS3_USBLIM_CGH = 0x02,
|
||||
PCF50633_MBCS3_TLIM_PLAY = 0x04,
|
||||
PCF50633_MBCS3_TLIM_CHG = 0x08,
|
||||
PCF50633_MBCS3_ILIM = 0x10, /* 1: Ibat > Icutoff */
|
||||
PCF50633_MBCS3_VLIM = 0x20, /* 1: Vbat == Vmax */
|
||||
PCF50633_MBCS3_VBATSTAT = 0x40, /* 1: Vbat > Vbatcond */
|
||||
PCF50633_MBCS3_VRES = 0x80, /* 1: Vbat > Vth(RES) */
|
||||
};
|
||||
|
||||
#define PCF50633_MBCC2_VBATCOND_MASK 0x03
|
||||
#define PCF50633_MBCC2_VMAX_MASK 0x3c
|
||||
|
||||
/* Charger status */
|
||||
#define PCF50633_MBC_USB_ONLINE 0x01
|
||||
#define PCF50633_MBC_USB_ACTIVE 0x02
|
||||
#define PCF50633_MBC_ADAPTER_ONLINE 0x04
|
||||
#define PCF50633_MBC_ADAPTER_ACTIVE 0x08
|
||||
|
||||
int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma);
|
||||
|
||||
int pcf50633_mbc_get_status(struct pcf50633 *);
|
||||
void pcf50633_mbc_set_status(struct pcf50633 *, int what, int status);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#ifndef __LINUX_MFD_PCF50633_PMIC_H
|
||||
#define __LINUX_MFD_PCF50633_PMIC_H
|
||||
|
||||
#include <linux/mfd/pcf50633/core.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define PCF50633_REG_AUTOOUT 0x1a
|
||||
#define PCF50633_REG_AUTOENA 0x1b
|
||||
#define PCF50633_REG_AUTOCTL 0x1c
|
||||
#define PCF50633_REG_AUTOMXC 0x1d
|
||||
#define PCF50633_REG_DOWN1OUT 0x1e
|
||||
#define PCF50633_REG_DOWN1ENA 0x1f
|
||||
#define PCF50633_REG_DOWN1CTL 0x20
|
||||
#define PCF50633_REG_DOWN1MXC 0x21
|
||||
#define PCF50633_REG_DOWN2OUT 0x22
|
||||
#define PCF50633_REG_DOWN2ENA 0x23
|
||||
#define PCF50633_REG_DOWN2CTL 0x24
|
||||
#define PCF50633_REG_DOWN2MXC 0x25
|
||||
#define PCF50633_REG_MEMLDOOUT 0x26
|
||||
#define PCF50633_REG_MEMLDOENA 0x27
|
||||
#define PCF50633_REG_LDO1OUT 0x2d
|
||||
#define PCF50633_REG_LDO1ENA 0x2e
|
||||
#define PCF50633_REG_LDO2OUT 0x2f
|
||||
#define PCF50633_REG_LDO2ENA 0x30
|
||||
#define PCF50633_REG_LDO3OUT 0x31
|
||||
#define PCF50633_REG_LDO3ENA 0x32
|
||||
#define PCF50633_REG_LDO4OUT 0x33
|
||||
#define PCF50633_REG_LDO4ENA 0x34
|
||||
#define PCF50633_REG_LDO5OUT 0x35
|
||||
#define PCF50633_REG_LDO5ENA 0x36
|
||||
#define PCF50633_REG_LDO6OUT 0x37
|
||||
#define PCF50633_REG_LDO6ENA 0x38
|
||||
#define PCF50633_REG_HCLDOOUT 0x39
|
||||
#define PCF50633_REG_HCLDOENA 0x3a
|
||||
#define PCF50633_REG_HCLDOOVL 0x40
|
||||
|
||||
enum pcf50633_regulator_enable {
|
||||
PCF50633_REGULATOR_ON = 0x01,
|
||||
PCF50633_REGULATOR_ON_GPIO1 = 0x02,
|
||||
PCF50633_REGULATOR_ON_GPIO2 = 0x04,
|
||||
PCF50633_REGULATOR_ON_GPIO3 = 0x08,
|
||||
};
|
||||
#define PCF50633_REGULATOR_ON_MASK 0x0f
|
||||
|
||||
enum pcf50633_regulator_phase {
|
||||
PCF50633_REGULATOR_ACTPH1 = 0x00,
|
||||
PCF50633_REGULATOR_ACTPH2 = 0x10,
|
||||
PCF50633_REGULATOR_ACTPH3 = 0x20,
|
||||
PCF50633_REGULATOR_ACTPH4 = 0x30,
|
||||
};
|
||||
#define PCF50633_REGULATOR_ACTPH_MASK 0x30
|
||||
|
||||
enum pcf50633_regulator_id {
|
||||
PCF50633_REGULATOR_AUTO,
|
||||
PCF50633_REGULATOR_DOWN1,
|
||||
PCF50633_REGULATOR_DOWN2,
|
||||
PCF50633_REGULATOR_LDO1,
|
||||
PCF50633_REGULATOR_LDO2,
|
||||
PCF50633_REGULATOR_LDO3,
|
||||
PCF50633_REGULATOR_LDO4,
|
||||
PCF50633_REGULATOR_LDO5,
|
||||
PCF50633_REGULATOR_LDO6,
|
||||
PCF50633_REGULATOR_HCLDO,
|
||||
PCF50633_REGULATOR_MEMLDO,
|
||||
};
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue