2010-08-06 02:28:27

by Joonyoung Shim

[permalink] [raw]
Subject: [PATCH v3 1/3] mfd: MAX8998: Use struct i2c_client to argument on i2c operation functions

The MAX8998 chip have regulator and rtc features. The i2c slave address
of regulator and rtc is different, so needs each i2c client on i2c
operation functions.

Also, this patch exports i2c operation functions instead of callback to
make easy to read.

Signed-off-by: Joonyoung Shim <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
Acked-by: Samuel Ortiz <[email protected]>
---
drivers/mfd/max8998.c | 29 ++++++++++++++---------------
drivers/regulator/max8998.c | 17 +++++++++++------
include/linux/mfd/max8998-private.h | 30 +++++-------------------------
3 files changed, 30 insertions(+), 46 deletions(-)

diff --git a/drivers/mfd/max8998.c b/drivers/mfd/max8998.c
index 0d68de2..183964e 100644
--- a/drivers/mfd/max8998.c
+++ b/drivers/mfd/max8998.c
@@ -36,13 +36,13 @@ static struct mfd_cell max8998_devs[] = {
}
};

-static int max8998_i2c_device_read(struct max8998_dev *max8998, u8 reg, u8 *dest)
+int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
{
- struct i2c_client *client = max8998->i2c_client;
+ struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
int ret;

mutex_lock(&max8998->iolock);
- ret = i2c_smbus_read_byte_data(client, reg);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
mutex_unlock(&max8998->iolock);
if (ret < 0)
return ret;
@@ -51,36 +51,38 @@ static int max8998_i2c_device_read(struct max8998_dev *max8998, u8 reg, u8 *dest
*dest = ret;
return 0;
}
+EXPORT_SYMBOL(max8998_read_reg);

-static int max8998_i2c_device_write(struct max8998_dev *max8998, u8 reg, u8 value)
+int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
{
- struct i2c_client *client = max8998->i2c_client;
+ struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
int ret;

mutex_lock(&max8998->iolock);
- ret = i2c_smbus_write_byte_data(client, reg, value);
+ ret = i2c_smbus_write_byte_data(i2c, reg, value);
mutex_unlock(&max8998->iolock);
return ret;
}
+EXPORT_SYMBOL(max8998_write_reg);

-static int max8998_i2c_device_update(struct max8998_dev *max8998, u8 reg,
- u8 val, u8 mask)
+int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
{
- struct i2c_client *client = max8998->i2c_client;
+ struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
int ret;

mutex_lock(&max8998->iolock);
- ret = i2c_smbus_read_byte_data(client, reg);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
if (ret >= 0) {
u8 old_val = ret & 0xff;
u8 new_val = (val & mask) | (old_val & (~mask));
- ret = i2c_smbus_write_byte_data(client, reg, new_val);
+ ret = i2c_smbus_write_byte_data(i2c, reg, new_val);
if (ret >= 0)
ret = 0;
}
mutex_unlock(&max8998->iolock);
return ret;
}
+EXPORT_SYMBOL(max8998_update_reg);

static int max8998_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
@@ -96,10 +98,7 @@ static int max8998_i2c_probe(struct i2c_client *i2c,

i2c_set_clientdata(i2c, max8998);
max8998->dev = &i2c->dev;
- max8998->i2c_client = i2c;
- max8998->dev_read = max8998_i2c_device_read;
- max8998->dev_write = max8998_i2c_device_write;
- max8998->dev_update = max8998_i2c_device_update;
+ max8998->i2c = i2c;
mutex_init(&max8998->iolock);

ret = mfd_add_devices(max8998->dev, -1,
diff --git a/drivers/regulator/max8998.c b/drivers/regulator/max8998.c
index 03e55a1..7fe9418 100644
--- a/drivers/regulator/max8998.c
+++ b/drivers/regulator/max8998.c
@@ -173,6 +173,7 @@ static int max8998_get_enable_register(struct regulator_dev *rdev,
static int max8998_ldo_is_enabled(struct regulator_dev *rdev)
{
struct max8998_data *max8998 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8998->iodev->i2c;
int ret, reg, shift = 8;
u8 val;

@@ -180,7 +181,7 @@ static int max8998_ldo_is_enabled(struct regulator_dev *rdev)
if (ret)
return ret;

- ret = max8998_read_reg(max8998->iodev, reg, &val);
+ ret = max8998_read_reg(i2c, reg, &val);
if (ret)
return ret;

@@ -190,25 +191,27 @@ static int max8998_ldo_is_enabled(struct regulator_dev *rdev)
static int max8998_ldo_enable(struct regulator_dev *rdev)
{
struct max8998_data *max8998 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8998->iodev->i2c;
int reg, shift = 8, ret;

ret = max8998_get_enable_register(rdev, &reg, &shift);
if (ret)
return ret;

- return max8998_update_reg(max8998->iodev, reg, 1<<shift, 1<<shift);
+ return max8998_update_reg(i2c, reg, 1<<shift, 1<<shift);
}

static int max8998_ldo_disable(struct regulator_dev *rdev)
{
struct max8998_data *max8998 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8998->iodev->i2c;
int reg, shift = 8, ret;

ret = max8998_get_enable_register(rdev, &reg, &shift);
if (ret)
return ret;

- return max8998_update_reg(max8998->iodev, reg, 0, 1<<shift);
+ return max8998_update_reg(i2c, reg, 0, 1<<shift);
}

static int max8998_get_voltage_register(struct regulator_dev *rdev,
@@ -276,6 +279,7 @@ static int max8998_get_voltage_register(struct regulator_dev *rdev,
static int max8998_get_voltage(struct regulator_dev *rdev)
{
struct max8998_data *max8998 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8998->iodev->i2c;
int reg, shift = 0, mask, ret;
u8 val;

@@ -283,7 +287,7 @@ static int max8998_get_voltage(struct regulator_dev *rdev)
if (ret)
return ret;

- ret = max8998_read_reg(max8998->iodev, reg, &val);
+ ret = max8998_read_reg(i2c, reg, &val);
if (ret)
return ret;

@@ -297,6 +301,7 @@ static int max8998_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV)
{
struct max8998_data *max8998 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8998->iodev->i2c;
int min_vol = min_uV / 1000, max_vol = max_uV / 1000;
int previous_vol = 0;
const struct voltage_map_desc *desc;
@@ -330,14 +335,14 @@ static int max8998_set_voltage(struct regulator_dev *rdev,
/* wait for RAMP_UP_DELAY if rdev is BUCK1/2 and
* ENRAMP is ON */
if (ldo == MAX8998_BUCK1 || ldo == MAX8998_BUCK2) {
- max8998_read_reg(max8998->iodev, MAX8998_REG_ONOFF4, &val);
+ max8998_read_reg(i2c, MAX8998_REG_ONOFF4, &val);
if (val & (1 << 4)) {
en_ramp = true;
previous_vol = max8998_get_voltage(rdev);
}
}

- ret = max8998_update_reg(max8998->iodev, reg, i<<shift, mask<<shift);
+ ret = max8998_update_reg(i2c, reg, i<<shift, mask<<shift);

if (en_ramp == true) {
int difference = desc->min + desc->step*i - previous_vol/1000;
diff --git a/include/linux/mfd/max8998-private.h b/include/linux/mfd/max8998-private.h
index 6dc75b3..f0a20cd 100644
--- a/include/linux/mfd/max8998-private.h
+++ b/include/linux/mfd/max8998-private.h
@@ -75,38 +75,18 @@ enum {
/**
* struct max8998_dev - max8998 master device for sub-drivers
* @dev: master device of the chip (can be used to access platform data)
- * @i2c_client: i2c client private data
- * @dev_read(): chip register read function
- * @dev_write(): chip register write function
- * @dev_update(): chip register update function
+ * @i2c: i2c client private data
* @iolock: mutex for serializing io access
*/

struct max8998_dev {
struct device *dev;
- struct i2c_client *i2c_client;
- int (*dev_read)(struct max8998_dev *max8998, u8 reg, u8 *dest);
- int (*dev_write)(struct max8998_dev *max8998, u8 reg, u8 val);
- int (*dev_update)(struct max8998_dev *max8998, u8 reg, u8 val, u8 mask);
+ struct i2c_client *i2c;
struct mutex iolock;
};

-static inline int max8998_read_reg(struct max8998_dev *max8998, u8 reg,
- u8 *value)
-{
- return max8998->dev_read(max8998, reg, value);
-}
-
-static inline int max8998_write_reg(struct max8998_dev *max8998, u8 reg,
- u8 value)
-{
- return max8998->dev_write(max8998, reg, value);
-}
-
-static inline int max8998_update_reg(struct max8998_dev *max8998, u8 reg,
- u8 value, u8 mask)
-{
- return max8998->dev_update(max8998, reg, value, mask);
-}
+extern int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
+extern int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
+extern int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);

#endif /* __LINUX_MFD_MAX8998_PRIV_H */
--
1.7.0.4


2010-08-06 02:28:30

by Joonyoung Shim

[permalink] [raw]
Subject: [PATCH v3 2/3] mfd: MAX8998: Add interrupts support

Use genirq and provide seperated file for interrupts support.

Signed-off-by: Joonyoung Shim <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
Reviewed-by: Mark Brown <[email protected]>
Acked-by: Samuel Ortiz <[email protected]>
---
drivers/mfd/Kconfig | 2 +-
drivers/mfd/Makefile | 2 +-
drivers/mfd/max8998-irq.c | 255 +++++++++++++++++++++++++++++++++++
drivers/mfd/max8998.c | 25 ++++-
include/linux/mfd/max8998-private.h | 72 ++++++++++-
include/linux/mfd/max8998.h | 11 +-
6 files changed, 358 insertions(+), 9 deletions(-)
create mode 100644 drivers/mfd/max8998-irq.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ad61a9e..ee2d1a7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -254,7 +254,7 @@ config MFD_MAX8925

config MFD_MAX8998
bool "Maxim Semiconductor MAX8998 PMIC Support"
- depends on I2C=y
+ depends on I2C=y && GENERIC_HARDIRQS
select MFD_CORE
help
Say yes here to support for Maxim Semiconductor MAX8998. This is
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a362ccf..565e021 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -56,7 +56,7 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o
max8925-objs := max8925-core.o max8925-i2c.o
obj-$(CONFIG_MFD_MAX8925) += max8925.o
-obj-$(CONFIG_MFD_MAX8998) += max8998.o
+obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o

pcf50633-objs := pcf50633-core.o pcf50633-irq.o
obj-$(CONFIG_MFD_PCF50633) += pcf50633.o
diff --git a/drivers/mfd/max8998-irq.c b/drivers/mfd/max8998-irq.c
new file mode 100644
index 0000000..346fd29
--- /dev/null
+++ b/drivers/mfd/max8998-irq.c
@@ -0,0 +1,255 @@
+/*
+ * Interrupt controller support for MAX8998
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <[email protected]>
+ *
+ * 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/device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/max8998-private.h>
+
+struct max8998_irq_data {
+ int reg;
+ int mask;
+};
+
+static struct max8998_irq_data max8998_irqs[] = {
+ [MAX8998_IRQ_DCINF] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_DCINF_MASK,
+ },
+ [MAX8998_IRQ_DCINR] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_DCINR_MASK,
+ },
+ [MAX8998_IRQ_JIGF] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_JIGF_MASK,
+ },
+ [MAX8998_IRQ_JIGR] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_JIGR_MASK,
+ },
+ [MAX8998_IRQ_PWRONF] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_PWRONF_MASK,
+ },
+ [MAX8998_IRQ_PWRONR] = {
+ .reg = 1,
+ .mask = MAX8998_IRQ_PWRONR_MASK,
+ },
+ [MAX8998_IRQ_WTSREVNT] = {
+ .reg = 2,
+ .mask = MAX8998_IRQ_WTSREVNT_MASK,
+ },
+ [MAX8998_IRQ_SMPLEVNT] = {
+ .reg = 2,
+ .mask = MAX8998_IRQ_SMPLEVNT_MASK,
+ },
+ [MAX8998_IRQ_ALARM1] = {
+ .reg = 2,
+ .mask = MAX8998_IRQ_ALARM1_MASK,
+ },
+ [MAX8998_IRQ_ALARM0] = {
+ .reg = 2,
+ .mask = MAX8998_IRQ_ALARM0_MASK,
+ },
+ [MAX8998_IRQ_ONKEY1S] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_ONKEY1S_MASK,
+ },
+ [MAX8998_IRQ_TOPOFFR] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_TOPOFFR_MASK,
+ },
+ [MAX8998_IRQ_DCINOVPR] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_DCINOVPR_MASK,
+ },
+ [MAX8998_IRQ_CHGRSTF] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_CHGRSTF_MASK,
+ },
+ [MAX8998_IRQ_DONER] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_DONER_MASK,
+ },
+ [MAX8998_IRQ_CHGFAULT] = {
+ .reg = 3,
+ .mask = MAX8998_IRQ_CHGFAULT_MASK,
+ },
+ [MAX8998_IRQ_LOBAT1] = {
+ .reg = 4,
+ .mask = MAX8998_IRQ_LOBAT1_MASK,
+ },
+ [MAX8998_IRQ_LOBAT2] = {
+ .reg = 4,
+ .mask = MAX8998_IRQ_LOBAT2_MASK,
+ },
+};
+
+static inline struct max8998_irq_data *
+irq_to_max8998_irq(struct max8998_dev *max8998, int irq)
+{
+ return &max8998_irqs[irq - max8998->irq_base];
+}
+
+static void max8998_irq_lock(unsigned int irq)
+{
+ struct max8998_dev *max8998 = get_irq_chip_data(irq);
+
+ mutex_lock(&max8998->irqlock);
+}
+
+static void max8998_irq_sync_unlock(unsigned int irq)
+{
+ struct max8998_dev *max8998 = get_irq_chip_data(irq);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(max8998->irq_masks_cur); i++) {
+ /*
+ * If there's been a change in the mask write it back
+ * to the hardware.
+ */
+ if (max8998->irq_masks_cur[i] != max8998->irq_masks_cache[i]) {
+ max8998->irq_masks_cache[i] = max8998->irq_masks_cur[i];
+ max8998_write_reg(max8998->i2c, MAX8998_REG_IRQM1 + i,
+ max8998->irq_masks_cur[i]);
+ }
+ }
+
+ mutex_unlock(&max8998->irqlock);
+}
+
+static void max8998_irq_unmask(unsigned int irq)
+{
+ struct max8998_dev *max8998 = get_irq_chip_data(irq);
+ struct max8998_irq_data *irq_data = irq_to_max8998_irq(max8998, irq);
+
+ max8998->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask;
+}
+
+static void max8998_irq_mask(unsigned int irq)
+{
+ struct max8998_dev *max8998 = get_irq_chip_data(irq);
+ struct max8998_irq_data *irq_data = irq_to_max8998_irq(max8998, irq);
+
+ max8998->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask;
+}
+
+static struct irq_chip max8998_irq_chip = {
+ .name = "max8998",
+ .bus_lock = max8998_irq_lock,
+ .bus_sync_unlock = max8998_irq_sync_unlock,
+ .mask = max8998_irq_mask,
+ .unmask = max8998_irq_unmask,
+};
+
+static irqreturn_t max8998_irq_thread(int irq, void *data)
+{
+ struct max8998_dev *max8998 = data;
+ u8 irq_reg[MAX8998_NUM_IRQ_REGS];
+ int ret;
+ int i;
+
+ ret = max8998_bulk_read(max8998->i2c, MAX8998_REG_IRQ1,
+ MAX8998_NUM_IRQ_REGS, irq_reg);
+ if (ret < 0) {
+ dev_err(max8998->dev, "Failed to read interrupt register: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ /* Apply masking */
+ for (i = 0; i < MAX8998_NUM_IRQ_REGS; i++)
+ irq_reg[i] &= ~max8998->irq_masks_cur[i];
+
+ /* Report */
+ for (i = 0; i < MAX8998_IRQ_NR; i++) {
+ if (irq_reg[max8998_irqs[i].reg - 1] & max8998_irqs[i].mask)
+ handle_nested_irq(max8998->irq_base + i);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int max8998_irq_init(struct max8998_dev *max8998)
+{
+ int i;
+ int cur_irq;
+ int ret;
+
+ if (!max8998->irq) {
+ dev_warn(max8998->dev,
+ "No interrupt specified, no interrupts\n");
+ max8998->irq_base = 0;
+ return 0;
+ }
+
+ if (!max8998->irq_base) {
+ dev_err(max8998->dev,
+ "No interrupt base specified, no interrupts\n");
+ return 0;
+ }
+
+ mutex_init(&max8998->irqlock);
+
+ /* Mask the individual interrupt sources */
+ for (i = 0; i < MAX8998_NUM_IRQ_REGS; i++) {
+ max8998->irq_masks_cur[i] = 0xff;
+ max8998->irq_masks_cache[i] = 0xff;
+ max8998_write_reg(max8998->i2c, MAX8998_REG_IRQM1 + i, 0xff);
+ }
+
+ max8998_write_reg(max8998->i2c, MAX8998_REG_STATUSM1, 0xff);
+ max8998_write_reg(max8998->i2c, MAX8998_REG_STATUSM2, 0xff);
+
+ /* register with genirq */
+ for (i = 0; i < MAX8998_IRQ_NR; i++) {
+ cur_irq = i + max8998->irq_base;
+ set_irq_chip_data(cur_irq, max8998);
+ set_irq_chip_and_handler(cur_irq, &max8998_irq_chip,
+ handle_edge_irq);
+ set_irq_nested_thread(cur_irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(cur_irq, IRQF_VALID);
+#else
+ set_irq_noprobe(cur_irq);
+#endif
+ }
+
+ ret = request_threaded_irq(max8998->irq, NULL, max8998_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "max8998-irq", max8998);
+ if (ret) {
+ dev_err(max8998->dev, "Failed to request IRQ %d: %d\n",
+ max8998->irq, ret);
+ return ret;
+ }
+
+ if (!max8998->ono)
+ return 0;
+
+ ret = request_threaded_irq(max8998->ono, NULL, max8998_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT, "max8998-ono", max8998);
+ if (ret)
+ dev_err(max8998->dev, "Failed to request IRQ %d: %d\n",
+ max8998->ono, ret);
+
+ return 0;
+}
+
+void max8998_irq_exit(struct max8998_dev *max8998)
+{
+ if (max8998->irq)
+ free_irq(max8998->irq, max8998);
+}
diff --git a/drivers/mfd/max8998.c b/drivers/mfd/max8998.c
index 183964e..7e4489f 100644
--- a/drivers/mfd/max8998.c
+++ b/drivers/mfd/max8998.c
@@ -1,5 +1,5 @@
/*
- * max8698.c - mfd core driver for the Maxim 8998
+ * max8998.c - mfd core driver for the Maxim 8998
*
* Copyright (C) 2009-2010 Samsung Electronics
* Kyungmin Park <[email protected]>
@@ -53,6 +53,21 @@ int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
}
EXPORT_SYMBOL(max8998_read_reg);

+int max8998_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8998->iolock);
+ ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max8998->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(max8998_bulk_read);
+
int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
{
struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
@@ -87,6 +102,7 @@ EXPORT_SYMBOL(max8998_update_reg);
static int max8998_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
+ struct max8998_platform_data *pdata = i2c->dev.platform_data;
struct max8998_dev *max8998;
int ret = 0;

@@ -99,8 +115,15 @@ static int max8998_i2c_probe(struct i2c_client *i2c,
i2c_set_clientdata(i2c, max8998);
max8998->dev = &i2c->dev;
max8998->i2c = i2c;
+ max8998->irq = i2c->irq;
+ if (pdata) {
+ max8998->ono = pdata->ono;
+ max8998->irq_base = pdata->irq_base;
+ }
mutex_init(&max8998->iolock);

+ max8998_irq_init(max8998);
+
ret = mfd_add_devices(max8998->dev, -1,
max8998_devs, ARRAY_SIZE(max8998_devs),
NULL, 0);
diff --git a/include/linux/mfd/max8998-private.h b/include/linux/mfd/max8998-private.h
index f0a20cd..3bd2371 100644
--- a/include/linux/mfd/max8998-private.h
+++ b/include/linux/mfd/max8998-private.h
@@ -1,5 +1,5 @@
/*
- * max8698.h - Voltage regulator driver for the Maxim 8998
+ * max8998.h - Voltage regulator driver for the Maxim 8998
*
* Copyright (C) 2009-2010 Samsung Electrnoics
* Kyungmin Park <[email protected]>
@@ -23,6 +23,8 @@
#ifndef __LINUX_MFD_MAX8998_PRIV_H
#define __LINUX_MFD_MAX8998_PRIV_H

+#define MAX8998_NUM_IRQ_REGS 4
+
/* MAX 8998 registers */
enum {
MAX8998_REG_IRQ1,
@@ -72,20 +74,86 @@ enum {
MAX8998_REG_LBCNFG2,
};

+/* IRQ definitions */
+enum {
+ MAX8998_IRQ_DCINF,
+ MAX8998_IRQ_DCINR,
+ MAX8998_IRQ_JIGF,
+ MAX8998_IRQ_JIGR,
+ MAX8998_IRQ_PWRONF,
+ MAX8998_IRQ_PWRONR,
+
+ MAX8998_IRQ_WTSREVNT,
+ MAX8998_IRQ_SMPLEVNT,
+ MAX8998_IRQ_ALARM1,
+ MAX8998_IRQ_ALARM0,
+
+ MAX8998_IRQ_ONKEY1S,
+ MAX8998_IRQ_TOPOFFR,
+ MAX8998_IRQ_DCINOVPR,
+ MAX8998_IRQ_CHGRSTF,
+ MAX8998_IRQ_DONER,
+ MAX8998_IRQ_CHGFAULT,
+
+ MAX8998_IRQ_LOBAT1,
+ MAX8998_IRQ_LOBAT2,
+
+ MAX8998_IRQ_NR,
+};
+
+#define MAX8998_IRQ_DCINF_MASK (1 << 2)
+#define MAX8998_IRQ_DCINR_MASK (1 << 3)
+#define MAX8998_IRQ_JIGF_MASK (1 << 4)
+#define MAX8998_IRQ_JIGR_MASK (1 << 5)
+#define MAX8998_IRQ_PWRONF_MASK (1 << 6)
+#define MAX8998_IRQ_PWRONR_MASK (1 << 7)
+
+#define MAX8998_IRQ_WTSREVNT_MASK (1 << 0)
+#define MAX8998_IRQ_SMPLEVNT_MASK (1 << 1)
+#define MAX8998_IRQ_ALARM1_MASK (1 << 2)
+#define MAX8998_IRQ_ALARM0_MASK (1 << 3)
+
+#define MAX8998_IRQ_ONKEY1S_MASK (1 << 0)
+#define MAX8998_IRQ_TOPOFFR_MASK (1 << 2)
+#define MAX8998_IRQ_DCINOVPR_MASK (1 << 3)
+#define MAX8998_IRQ_CHGRSTF_MASK (1 << 4)
+#define MAX8998_IRQ_DONER_MASK (1 << 5)
+#define MAX8998_IRQ_CHGFAULT_MASK (1 << 7)
+
+#define MAX8998_IRQ_LOBAT1_MASK (1 << 0)
+#define MAX8998_IRQ_LOBAT2_MASK (1 << 1)
+
/**
* struct max8998_dev - max8998 master device for sub-drivers
* @dev: master device of the chip (can be used to access platform data)
* @i2c: i2c client private data
* @iolock: mutex for serializing io access
+ * @irqlock: mutex for buslock
+ * @irq_base: base IRQ number for max8998, required for IRQs
+ * @irq: generic IRQ number for max8998
+ * @ono: power onoff IRQ number for max8998
+ * @irq_masks_cur: currently active value
+ * @irq_masks_cache: cached hardware value
*/
-
struct max8998_dev {
struct device *dev;
struct i2c_client *i2c;
struct mutex iolock;
+ struct mutex irqlock;
+
+ int irq_base;
+ int irq;
+ int ono;
+ u8 irq_masks_cur[MAX8998_NUM_IRQ_REGS];
+ u8 irq_masks_cache[MAX8998_NUM_IRQ_REGS];
};

+int max8998_irq_init(struct max8998_dev *max8998);
+void max8998_irq_exit(struct max8998_dev *max8998);
+
extern int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
+extern int max8998_bulk_read(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
extern int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
extern int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);

diff --git a/include/linux/mfd/max8998.h b/include/linux/mfd/max8998.h
index 1d3601a..d47ed4c 100644
--- a/include/linux/mfd/max8998.h
+++ b/include/linux/mfd/max8998.h
@@ -1,5 +1,5 @@
/*
- * max8698.h - Voltage regulator driver for the Maxim 8998
+ * max8998.h - Voltage regulator driver for the Maxim 8998
*
* Copyright (C) 2009-2010 Samsung Electrnoics
* Kyungmin Park <[email protected]>
@@ -66,13 +66,16 @@ struct max8998_regulator_data {

/**
* struct max8998_board - packages regulator init data
- * @num_regulators: number of regultors used
* @regulators: array of defined regulators
+ * @num_regulators: number of regultors used
+ * @irq_base: base IRQ number for max8998, required for IRQs
+ * @ono: power onoff IRQ number for max8998
*/
-
struct max8998_platform_data {
- int num_regulators;
struct max8998_regulator_data *regulators;
+ int num_regulators;
+ int irq_base;
+ int ono;
};

#endif /* __LINUX_MFD_MAX8998_H */
--
1.7.0.4

2010-08-06 02:28:56

by Joonyoung Shim

[permalink] [raw]
Subject: [PATCH v3 3/3] rtc: Add MAX8998 rtc driver

This adds support for the RTC provided by the Maxim 8998 chip. This
driver was tested on a GONI board by using the rtc-test application from
the Documentation/rtc.txt.

Signed-off-by: Joonyoung Shim <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
Acked-by: Samuel Ortiz <[email protected]>
Acked-by: Alessandro Zummo <[email protected]>
---
drivers/mfd/max8998.c | 24 +++-
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-max8998.c | 300 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8998-private.h | 6 +-
5 files changed, 339 insertions(+), 2 deletions(-)
create mode 100644 drivers/rtc/rtc-max8998.c

diff --git a/drivers/mfd/max8998.c b/drivers/mfd/max8998.c
index 7e4489f..5488672 100644
--- a/drivers/mfd/max8998.c
+++ b/drivers/mfd/max8998.c
@@ -30,10 +30,14 @@
#include <linux/mfd/max8998.h>
#include <linux/mfd/max8998-private.h>

+#define RTC_I2C_ADDR (0x0c >> 1)
+
static struct mfd_cell max8998_devs[] = {
{
.name = "max8998-pmic",
- }
+ }, {
+ .name = "max8998-rtc",
+ },
};

int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
@@ -80,6 +84,21 @@ int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
}
EXPORT_SYMBOL(max8998_write_reg);

+int max8998_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8998->iolock);
+ ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max8998->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(max8998_bulk_write);
+
int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
{
struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
@@ -122,6 +141,9 @@ static int max8998_i2c_probe(struct i2c_client *i2c,
}
mutex_init(&max8998->iolock);

+ max8998->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR);
+ i2c_set_clientdata(max8998->rtc, max8998);
+
max8998_irq_init(max8998);

ret = mfd_add_devices(max8998->dev, -1,
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 10ba12c..fda81e4 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -185,6 +185,16 @@ config RTC_DRV_MAX8925
This driver can also be built as a module. If so, the module
will be called rtc-max8925.

+config RTC_DRV_MAX8998
+ tristate "Maxim MAX8998"
+ depends on MFD_MAX8998
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX8998 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max8998.
+
config RTC_DRV_RS5C372
tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A"
help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 5adbba7..f677bb0 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
obj-$(CONFIG_RTC_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
+obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MC13783) += rtc-mc13783.o
obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o
diff --git a/drivers/rtc/rtc-max8998.c b/drivers/rtc/rtc-max8998.c
new file mode 100644
index 0000000..f22dee3
--- /dev/null
+++ b/drivers/rtc/rtc-max8998.c
@@ -0,0 +1,300 @@
+/*
+ * RTC driver for Maxim MAX8998
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Author: Minkyu Kang <[email protected]>
+ * Author: Joonyoung Shim <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max8998.h>
+#include <linux/mfd/max8998-private.h>
+
+#define MAX8998_RTC_SEC 0x00
+#define MAX8998_RTC_MIN 0x01
+#define MAX8998_RTC_HOUR 0x02
+#define MAX8998_RTC_WEEKDAY 0x03
+#define MAX8998_RTC_DATE 0x04
+#define MAX8998_RTC_MONTH 0x05
+#define MAX8998_RTC_YEAR1 0x06
+#define MAX8998_RTC_YEAR2 0x07
+#define MAX8998_ALARM0_SEC 0x08
+#define MAX8998_ALARM0_MIN 0x09
+#define MAX8998_ALARM0_HOUR 0x0a
+#define MAX8998_ALARM0_WEEKDAY 0x0b
+#define MAX8998_ALARM0_DATE 0x0c
+#define MAX8998_ALARM0_MONTH 0x0d
+#define MAX8998_ALARM0_YEAR1 0x0e
+#define MAX8998_ALARM0_YEAR2 0x0f
+#define MAX8998_ALARM1_SEC 0x10
+#define MAX8998_ALARM1_MIN 0x11
+#define MAX8998_ALARM1_HOUR 0x12
+#define MAX8998_ALARM1_WEEKDAY 0x13
+#define MAX8998_ALARM1_DATE 0x14
+#define MAX8998_ALARM1_MONTH 0x15
+#define MAX8998_ALARM1_YEAR1 0x16
+#define MAX8998_ALARM1_YEAR2 0x17
+#define MAX8998_ALARM0_CONF 0x18
+#define MAX8998_ALARM1_CONF 0x19
+#define MAX8998_RTC_STATUS 0x1a
+#define MAX8998_WTSR_SMPL_CNTL 0x1b
+#define MAX8998_TEST 0x1f
+
+#define HOUR_12 (1 << 7)
+#define HOUR_PM (1 << 5)
+#define ALARM0_STATUS (1 << 1)
+#define ALARM1_STATUS (1 << 2)
+
+enum {
+ RTC_SEC = 0,
+ RTC_MIN,
+ RTC_HOUR,
+ RTC_WEEKDAY,
+ RTC_DATE,
+ RTC_MONTH,
+ RTC_YEAR1,
+ RTC_YEAR2,
+};
+
+struct max8998_rtc_info {
+ struct device *dev;
+ struct max8998_dev *max8998;
+ struct i2c_client *rtc;
+ struct rtc_device *rtc_dev;
+ int irq;
+};
+
+static void max8998_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ tm->tm_sec = bcd2bin(data[RTC_SEC]);
+ tm->tm_min = bcd2bin(data[RTC_MIN]);
+ if (data[RTC_HOUR] & HOUR_12) {
+ tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x1f);
+ if (data[RTC_HOUR] & HOUR_PM)
+ tm->tm_hour += 12;
+ } else
+ tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f);
+
+ tm->tm_wday = data[RTC_WEEKDAY] & 0x07;
+ tm->tm_mday = bcd2bin(data[RTC_DATE]);
+ tm->tm_mon = bcd2bin(data[RTC_MONTH]);
+ tm->tm_year = bcd2bin(data[RTC_YEAR1]) + bcd2bin(data[RTC_YEAR2]) * 100;
+ tm->tm_year -= 1900;
+}
+
+static void max8998_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = bin2bcd(tm->tm_sec);
+ data[RTC_MIN] = bin2bcd(tm->tm_min);
+ data[RTC_HOUR] = bin2bcd(tm->tm_hour);
+ data[RTC_WEEKDAY] = tm->tm_wday;
+ data[RTC_DATE] = bin2bcd(tm->tm_mday);
+ data[RTC_MONTH] = bin2bcd(tm->tm_mon);
+ data[RTC_YEAR1] = bin2bcd(tm->tm_year % 100);
+ data[RTC_YEAR2] = bin2bcd((tm->tm_year + 1900) / 100);
+}
+
+static int max8998_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max8998_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ ret = max8998_bulk_read(info->rtc, MAX8998_RTC_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ max8998_data_to_tm(data, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int max8998_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max8998_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+
+ max8998_tm_to_data(tm, data);
+
+ return max8998_bulk_write(info->rtc, MAX8998_RTC_SEC, 8, data);
+}
+
+static int max8998_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max8998_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ u8 val;
+ int ret;
+
+ ret = max8998_bulk_read(info->rtc, MAX8998_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ max8998_data_to_tm(data, &alrm->time);
+
+ ret = max8998_read_reg(info->rtc, MAX8998_ALARM0_CONF, &val);
+ if (ret < 0)
+ return ret;
+
+ alrm->enabled = !!val;
+
+ ret = max8998_read_reg(info->rtc, MAX8998_RTC_STATUS, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & ALARM0_STATUS)
+ alrm->pending = 1;
+ else
+ alrm->pending = 0;
+
+ return 0;
+}
+
+static int max8998_rtc_stop_alarm(struct max8998_rtc_info *info)
+{
+ return max8998_write_reg(info->rtc, MAX8998_ALARM0_CONF, 0);
+}
+
+static int max8998_rtc_start_alarm(struct max8998_rtc_info *info)
+{
+ return max8998_write_reg(info->rtc, MAX8998_ALARM0_CONF, 0x77);
+}
+
+static int max8998_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max8998_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ max8998_tm_to_data(&alrm->time, data);
+
+ ret = max8998_rtc_stop_alarm(info);
+ if (ret < 0)
+ return ret;
+
+ ret = max8998_bulk_write(info->rtc, MAX8998_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ if (alrm->enabled)
+ return max8998_rtc_start_alarm(info);
+
+ return 0;
+}
+
+static int max8998_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct max8998_rtc_info *info = dev_get_drvdata(dev);
+
+ if (enabled)
+ return max8998_rtc_start_alarm(info);
+ else
+ return max8998_rtc_stop_alarm(info);
+}
+
+static irqreturn_t max8998_rtc_alarm_irq(int irq, void *data)
+{
+ struct max8998_rtc_info *info = data;
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops max8998_rtc_ops = {
+ .read_time = max8998_rtc_read_time,
+ .set_time = max8998_rtc_set_time,
+ .read_alarm = max8998_rtc_read_alarm,
+ .set_alarm = max8998_rtc_set_alarm,
+ .alarm_irq_enable = max8998_rtc_alarm_irq_enable,
+};
+
+static int __devinit max8998_rtc_probe(struct platform_device *pdev)
+{
+ struct max8998_dev *max8998 = dev_get_drvdata(pdev->dev.parent);
+ struct max8998_rtc_info *info;
+ int ret;
+
+ info = kzalloc(sizeof(struct max8998_rtc_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = &pdev->dev;
+ info->max8998 = max8998;
+ info->rtc = max8998->rtc;
+ info->irq = max8998->irq_base + MAX8998_IRQ_ALARM0;
+
+ info->rtc_dev = rtc_device_register("max8998-rtc", &pdev->dev,
+ &max8998_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(info->rtc_dev)) {
+ ret = PTR_ERR(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ goto out_rtc;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ ret = request_threaded_irq(info->irq, NULL, max8998_rtc_alarm_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+ info->irq, ret);
+
+ return 0;
+
+out_rtc:
+ kfree(info);
+ return ret;
+}
+
+static int __devexit max8998_rtc_remove(struct platform_device *pdev)
+{
+ struct max8998_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ free_irq(info->irq, info);
+ rtc_device_unregister(info->rtc_dev);
+ kfree(info);
+ }
+
+ return 0;
+}
+
+static struct platform_driver max8998_rtc_driver = {
+ .driver = {
+ .name = "max8998-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = max8998_rtc_probe,
+ .remove = __devexit_p(max8998_rtc_remove),
+};
+
+static int __init max8998_rtc_init(void)
+{
+ return platform_driver_register(&max8998_rtc_driver);
+}
+module_init(max8998_rtc_init);
+
+static void __exit max8998_rtc_exit(void)
+{
+ platform_driver_unregister(&max8998_rtc_driver);
+}
+module_exit(max8998_rtc_exit);
+
+MODULE_AUTHOR("Minkyu Kang <[email protected]>");
+MODULE_AUTHOR("Joonyoung Shim <[email protected]>");
+MODULE_DESCRIPTION("Maxim MAX8998 RTC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max8998-private.h b/include/linux/mfd/max8998-private.h
index 3bd2371..170f665 100644
--- a/include/linux/mfd/max8998-private.h
+++ b/include/linux/mfd/max8998-private.h
@@ -126,7 +126,8 @@ enum {
/**
* struct max8998_dev - max8998 master device for sub-drivers
* @dev: master device of the chip (can be used to access platform data)
- * @i2c: i2c client private data
+ * @i2c: i2c client private data for regulator
+ * @rtc: i2c client private data for rtc
* @iolock: mutex for serializing io access
* @irqlock: mutex for buslock
* @irq_base: base IRQ number for max8998, required for IRQs
@@ -138,6 +139,7 @@ enum {
struct max8998_dev {
struct device *dev;
struct i2c_client *i2c;
+ struct i2c_client *rtc;
struct mutex iolock;
struct mutex irqlock;

@@ -155,6 +157,8 @@ extern int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
extern int max8998_bulk_read(struct i2c_client *i2c, u8 reg, int count,
u8 *buf);
extern int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
+extern int max8998_bulk_write(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
extern int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);

#endif /* __LINUX_MFD_MAX8998_PRIV_H */
--
1.7.0.4