You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
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:
@@ -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");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
+28
-2
@@ -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");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user