blob: 3a6a971101ba23c58a17985809246a231438cd25 [file] [log] [blame]
/**
* linux/drivers/parrot/mfd/p7mu.c - Parrot7 power management unit core driver
* implementation
*
* Copyright (C) 2012 Parrot S.A.
*
* author: Gregor Boirie <gregor.boirie@parrot.com>
* date: 10-May-2012
*
* This file is released under the GPL
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/ratelimit.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/pm.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/mfd/core.h>
#include <linux/fs.h>
#include <asm/system_misc.h>
#include <asm/proc-fns.h>
#include <asm/smp_plat.h>
#include "p7mu.h"
#include "p7mu-pin.h"
#include "p7mu-clk.h"
/*
* Configuration block registers
*/
#define P7MU_CFG_DCDC0_CFG_0_REG (P7MU_CFG_PAGE + 0x00)
#define P7MU_CFG_DCDC0_CFG_1_REG (P7MU_CFG_PAGE + 0x01)
#define P7MU_CFG_DCDC1_CFG_0_REG (P7MU_CFG_PAGE + 0x02)
#define P7MU_CFG_DCDC1_CFG_1_REG (P7MU_CFG_PAGE + 0x03)
#define P7MU_CFG_DCDC2_CFG_0_REG (P7MU_CFG_PAGE + 0x04)
#define P7MU_CFG_DCDC2_CFG_1_REG (P7MU_CFG_PAGE + 0x05)
#define P7MU_CFG_DCDC3_CFG_0_REG (P7MU_CFG_PAGE + 0x06)
#define P7MU_CFG_DCDC3_CFG_1_REG (P7MU_CFG_PAGE + 0x07)
#define P7MU_CFG_DCDC_PGATE_DEL_REG (P7MU_CFG_PAGE + 0x08)
#define P7MU_CFG_DCDC_NGATE_DEL_REG (P7MU_CFG_PAGE + 0x09)
#define P7MU_CFG_LDOS_CFG_REG (P7MU_CFG_PAGE + 0x0a)
#define P7MU_CFG_TS_CAL_REG (P7MU_CFG_PAGE + 0x0d)
#define P7MU_CFG_TS_POINT_0_REG (P7MU_CFG_PAGE + 0x0e)
#define P7MU_CFG_TS_POINT_1_REG (P7MU_CFG_PAGE + 0x0f)
#define P7MU_CFG_CORE_CFG_REG (P7MU_CFG_PAGE + 0x10)
#define P7MU_CFG_TEST_MUX0_REG (P7MU_CFG_PAGE + 0x11)
#define P7MU_CFG_TEST_MUX1_REG (P7MU_CFG_PAGE + 0x12)
#define P7MU_CFG_TEST_MUX2_REG (P7MU_CFG_PAGE + 0x13)
#define P7MU_CFG_TEST_MUX3_REG (P7MU_CFG_PAGE + 0x14)
#define P7MU_CFG_TEST_MUX4_REG (P7MU_CFG_PAGE + 0x15)
#define P7MU_CFG_TEST_MUX5_REG (P7MU_CFG_PAGE + 0x16)
#define P7MU_CFG_TEST_MUX6_REG (P7MU_CFG_PAGE + 0x17)
#define P7MU_CFG_TEST_MUX7_REG (P7MU_CFG_PAGE + 0x18)
#define P7MU_CFG_TEST_DDR_CFG_REG (P7MU_CFG_PAGE + 0x19)
#define P7MU_CFG_ID_0_REG (P7MU_CFG_PAGE + 0x1a)
#define P7MU_CFG_ID_1_REG (P7MU_CFG_PAGE + 0x1b)
#define P7MU_CFG_ID_2_REG (P7MU_CFG_PAGE + 0x1c)
#define P7MU_CFG_ID_3_REG (P7MU_CFG_PAGE + 0x1d)
#define P7MU_CFG_ID P7MU_CFG_ID_0_REG
/*
* Power Management Control block registers
*/
#define P7MU_PMC_PAGE ((u16) 0x0100)
#define P7MU_PMC_POWER_SUPPLY_CTRL_REG ((u16) P7MU_PMC_PAGE + 0x00)
#define P7MU_PMC_GPIO_EVENT_SEL_REG ((u16) P7MU_PMC_PAGE + 0x01)
#define P7MU_PMC_GPIO_REG ((u16) P7MU_PMC_PAGE + 0x02)
#define P7MU_PMC_INTERRUPT_0_REG ((u16) P7MU_PMC_PAGE + 0x03)
#define P7MU_PMC_INTERRUPT_1_REG ((u16) P7MU_PMC_PAGE + 0x04)
#define P7MU_PMC_INTERRUPT_EN_0_REG ((u16) P7MU_PMC_PAGE + 0x05)
#define P7MU_PMC_INTERRUPT_EN_1_REG ((u16) P7MU_PMC_PAGE + 0x06)
#define P7MU_PMC_CMD_REG ((u16) P7MU_PMC_PAGE + 0x07)
#define P7MU_PMC_DEBUG_REG ((u16) P7MU_PMC_PAGE + 0x08)
#define P7MU_PMC_STATUS_REG ((u16) P7MU_PMC_PAGE + 0x09)
#define P7MU_PMC_TEMP_ERR_CNT_REG ((u16) P7MU_PMC_PAGE + 0x0a)
#define P7MU_PMC_TEMP_ERR_CTRL_REG ((u16) P7MU_PMC_PAGE + 0x0b)
#define P7MU_PMC_TEMP_ERR_TIME_REG ((u16) P7MU_PMC_PAGE + 0x0c)
#define P7MU_PMC_POWER_SUPPLY_MASK_REG ((u16) P7MU_PMC_PAGE + 0x0d)
#define P7MU_PMC_POWER_SUPPLY_FILTER_REG ((u16) P7MU_PMC_PAGE + 0x0e)
#define P7MU_PMC_POWER_SUPPLY_STATUS_REG ((u16) P7MU_PMC_PAGE + 0x0f)
#define P7MU_PMC_POWER_SUPPLY_CTRL_IDLE_REG ((u16) P7MU_PMC_PAGE + 0x10)
#define P7MU_CMD_SHUTDOWN ((u16) (1U << 0))
#define P7MU_CMD_STANDBY ((u16) (1U << 1))
#define P7MU_CMD_REBOOT ((u16) (1U << 2))
#define P7MU_CMD_RESET ((u16) (1U << 3))
#define P7MU_CMD_RUN ((u16) (1U << 4))
#define P7MU_INT_PWREN ((u16) (1U << 0))
#define P7MU_INT_TEMP_WARN ((u16) (1U << 1))
#define P7MU_INT_TEMP_ERR ((u16) (1U << 2))
#define P7MU_INT_GPIO_1 ((u16) (1U << 3))
#define P7MU_INT_GPIO_2 ((u16) (1U << 4))
#define P7MU_INT_GPIO_3 ((u16) (1U << 5))
#define P7MU_INT_RTC ((u16) (1U << 6))
#define P7MU_INT_MEASUREMENT_ERROR ((u16) (1U << 7))
#define P7MU_INT_FIFO_OVERFLOW ((u16) (1U << 8))
#define P7MU_INT_DET0_DOWN ((u16) (1U << 9))
#define P7MU_INT_DET0_UP ((u16) (1U << 10))
#define P7MU_INT_DET1_DOWN ((u16) (1U << 11))
#define P7MU_INT_DET1_UP ((u16) (1U << 12))
#define P7MU_INT_POWER ((u16) (1U << 13))
#define P7MU_INT_REBOOT ((u16) (1U << 14))
#define P7MU_INT_EN ((u16) (1U << 15))
#define P7MU_INT_GPIO_4 ((u16) (1U << 0))
#define P7MU_INT_GPIO_5 ((u16) (1U << 1))
#define P7MU_INT_DET2_DOWN ((u16) (1U << 2))
#define P7MU_INT_DET2_UP ((u16) (1U << 3))
#define P7MU_INT_DET3_DOWN ((u16) (1U << 4))
#define P7MU_INT_DET3_UP ((u16) (1U << 5))
/*
* RTC block registers
*/
#define P7MU_RTC_PAGE ((u16) 0x0600)
#define P7MU_RTC_SZ 7
/*
* PWM block registers
*/
#define P7MU_PWM0_PAGE ((u16) 0x0700)
#define P7MU_PWM1_PAGE ((u16) 0x0800)
#define P7MU_PWM_SZ 3
/*
* ADC block registers
*/
#define P7MU_ADC_PAGE ((u16) 0x0500)
#define P7MU_ADC_SZ 0x34
/*
* SPI block register
*/
#define P7MU_SPI_PAGE ((u16) 0x0400)
#define P7MU_SPI_SZ 2
/*
* Application register
*/
#define P7MU_APPL_PAGE ((u16) 0x900)
#define P7MU_APPL_SZ 16
#define P7MU_APPL_DATA00_REG (P7MU_APPL_PAGE + 0x00)
#define P7MU_APPL_DATA01_REG (P7MU_APPL_PAGE + 0x01)
#define P7MU_APPL_DATA02_REG (P7MU_APPL_PAGE + 0x02)
#define P7MU_APPL_DATA03_REG (P7MU_APPL_PAGE + 0x03)
#define P7MU_APPL_DATA04_REG (P7MU_APPL_PAGE + 0x04)
#define P7MU_APPL_DATA05_REG (P7MU_APPL_PAGE + 0x05)
#define P7MU_APPL_DATA06_REG (P7MU_APPL_PAGE + 0x06)
#define P7MU_APPL_DATA07_REG (P7MU_APPL_PAGE + 0x07)
#define P7MU_APPL_DATA08_REG (P7MU_APPL_PAGE + 0x08)
#define P7MU_APPL_DATA09_REG (P7MU_APPL_PAGE + 0x09)
#define P7MU_APPL_DATA10_REG (P7MU_APPL_PAGE + 0x0a)
#define P7MU_APPL_DATA11_REG (P7MU_APPL_PAGE + 0x0b)
#define P7MU_APPL_DATA12_REG (P7MU_APPL_PAGE + 0x0c)
#define P7MU_APPL_DATA13_REG (P7MU_APPL_PAGE + 0x0d)
#define P7MU_APPL_DATA14_REG (P7MU_APPL_PAGE + 0x0e)
#define P7MU_APPL_DATA15_REG (P7MU_APPL_PAGE + 0x0f)
#define P7MU_REG_MAX ((u16) 0x0f0c)
/***********************
* Global declarations.
***********************/
#define P7MU_PWREN_IRQ 0
#define P7MU_TEMP_WARN_IRQ 1
#define P7MU_TEMP_ERR_IRQ 2
#define P7MU_GPIO_1_IRQ 3
#define P7MU_GPIO_2_IRQ 4
#define P7MU_GPIO_3_IRQ 5
#define P7MU_RTC_IRQ 6
#define P7MU_MEASUREMENT_ERROR_IRQ 7
#define P7MU_FIFO_OVERFLOW_IRQ 8
#define P7MU_DET0_DOWN_IRQ 9
#define P7MU_DET0_UP_IRQ 10
#define P7MU_DET1_DOWN_IRQ 11
#define P7MU_DET1_UP_IRQ 12
#define P7MU_POWER_IRQ 13
#define P7MU_REBOOT_IRQ 14
#define P7MU_GPIO_4_IRQ 16
#define P7MU_GPIO_5_IRQ 17
#define P7MU_DET2_DOWN_IRQ 18
#define P7MU_DET2_UP_IRQ 19
#define P7MU_DET3_DOWN_IRQ 20
#define P7MU_DET3_UP_IRQ 21
#define P7MU_IRQ_NR 22
#define DEFAULT_IRQ (P7MU_INT_EN|P7MU_INT_TEMP_WARN|P7MU_INT_TEMP_ERR|P7MU_INT_POWER|P7MU_INT_PWREN|P7MU_INT_REBOOT|P7MU_INT_POWER)
static struct resource p7mu_iores = {
.name = P7MU_DRV_NAME " io",
.start = 0,
.end = P7MU_REG_MAX,
.flags = IORESOURCE_IO | IORESOURCE_BUSY
};
static struct resource p7mu_irqres = {
.name = P7MU_DRV_NAME " irq",
.start = 0,
.end = P7MU_IRQ_NR - 1,
.flags = IORESOURCE_IO | IORESOURCE_BUSY
};
static struct resource p7mu_rtc_res[] __devinitdata = {
{
.start = P7MU_RTC_IRQ,
.end = P7MU_RTC_IRQ,
.flags = IORESOURCE_IRQ,
.parent = &p7mu_irqres
},
{
.start = P7MU_RTC_PAGE,
.end = P7MU_RTC_PAGE + P7MU_RTC_SZ - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
}
};
static struct resource p7mu_gpio_res[] __devinitdata = {
{
.start = P7MU_PMC_GPIO_REG,
.end = P7MU_PMC_GPIO_REG + sizeof(u16) - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
}
};
static struct resource p7mu_adc_res[] __devinitdata = {
{ /* ADC */
.start = P7MU_ADC_PAGE,
.end = P7MU_ADC_PAGE + P7MU_ADC_SZ - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
},
{ /* SPI */
.start = P7MU_SPI_PAGE,
.end = P7MU_SPI_PAGE + sizeof(u16) - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
}
};
static struct resource p7mu_pwm_res[] __devinitdata = {
{ /* PWM0 */
.start = P7MU_PWM0_PAGE,
.end = P7MU_PWM0_PAGE + P7MU_PWM_SZ - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
},
{ /* PWM1 */
.start = P7MU_PWM1_PAGE,
.end = P7MU_PWM1_PAGE + P7MU_PWM_SZ - 1,
.flags = IORESOURCE_IO,
.parent = &p7mu_iores
}
};
static struct mfd_cell p7mu_cells[] __devinitdata = {
{ /* RTC */
.name = P7MU_DRV_NAME "-rtc",
.id = -1,
.resources = p7mu_rtc_res,
.num_resources = ARRAY_SIZE(p7mu_rtc_res)
},
{ /* GPIO */
.name = P7MU_DRV_NAME "-gpio",
.id = -1,
.resources = p7mu_gpio_res,
.num_resources = ARRAY_SIZE(p7mu_gpio_res)
},
{ /* ADC */
.name = P7MU_DRV_NAME "-adc",
.id = -1,
.resources = p7mu_adc_res,
.num_resources = ARRAY_SIZE(p7mu_adc_res)
},
{ /* PWM */
.name = P7MU_DRV_NAME "-pwm",
.id = -1,
.resources = p7mu_pwm_res,
.num_resources = ARRAY_SIZE(p7mu_pwm_res)
}
};
struct p7mu_irq {
u16 const msk;
unsigned const reg:1;
const char *name;
};
#define P7MU_INIT_IRQ(_name, _reg) \
[P7MU_ ## _name ## _IRQ] = { .msk = P7MU_INT_ ## _name, .reg = _reg, .name = #_name }
static struct p7mu_irq p7mu_irqs[] = {
/* Interrupt register 0 */
P7MU_INIT_IRQ(PWREN, 0),
P7MU_INIT_IRQ(TEMP_WARN, 0),
P7MU_INIT_IRQ(TEMP_ERR, 0),
P7MU_INIT_IRQ(GPIO_1, 0),
P7MU_INIT_IRQ(GPIO_2, 0),
P7MU_INIT_IRQ(GPIO_3, 0),
P7MU_INIT_IRQ(RTC, 0),
P7MU_INIT_IRQ(MEASUREMENT_ERROR, 0),
P7MU_INIT_IRQ(FIFO_OVERFLOW, 0),
P7MU_INIT_IRQ(DET0_DOWN, 0),
P7MU_INIT_IRQ(DET0_UP, 0),
P7MU_INIT_IRQ(DET1_DOWN, 0),
P7MU_INIT_IRQ(DET1_UP, 0),
P7MU_INIT_IRQ(POWER, 0),
P7MU_INIT_IRQ(REBOOT, 0),
/* Interrupt register 1 */
P7MU_INIT_IRQ(GPIO_4, 1),
P7MU_INIT_IRQ(GPIO_5, 1),
P7MU_INIT_IRQ(DET2_DOWN, 1),
P7MU_INIT_IRQ(DET2_UP, 1),
P7MU_INIT_IRQ(DET3_DOWN, 1),
P7MU_INIT_IRQ(DET3_UP, 1)
};
struct i2c_client* p7mu_i2c;
EXPORT_SYMBOL(p7mu_i2c);
static struct pinctrl* p7mu_pctl;
struct p7mu_chip {
struct mutex irq_lock;
uint32_t irq_mask;
uint32_t irq_en;
int irq_base;
u16 gpio_ev_update;
u16 gpio_ev;
} p7mu_chip;
#define P7MU_BOOT_REASON_FIRST_BOOT 0
#define P7MU_BOOT_REASON_SOFT_REBOOT 1
#define P7MU_BOOT_REASON_HARD_REBOOT 2
#define P7MU_BOOT_REASON_P7MU_PWREN 3
#define P7MU_BOOT_REASON_P7MU_REBOOT 4
#define P7MU_BOOT_REASON_P7MU_POWER 5
#define P7MU_BOOT_REASON_OTHER 6
static u16 boot_reason;
static u16 irq_before_reboot = 0;
static u16 pending_irq = 0;
static u16 power_supply_status = 0;
static u16 status_register = 0;
static u16 temperature_error_count = 0;
static char *boot_reasons_str[] = {
"first boot",
"soft reboot",
"hard reboot",
"pwr_en boot",
"p7mu reboot",
"power reboot",
"other",
};
int p7mu_transfer(u16 offset, u16* buff, size_t cnt, bool read)
{
struct i2c_msg msg[2];
int err;
#ifdef DEBUG
BUG_ON(! p7mu_i2c);
BUG_ON((cnt * sizeof(*buff)) > (size_t) ((u16) (~0U)));
#endif
msg[0].addr = p7mu_i2c->addr;
msg[0].flags = 0;
msg[0].len = (u16) sizeof(offset);
msg[0].buf = (u8*) &offset;
msg[1].addr = p7mu_i2c->addr;
msg[1].flags = read ? I2C_M_RD : I2C_M_NOSTART;
msg[1].len = (u16) (cnt * sizeof(*buff));
msg[1].buf = (u8*) buff;
err = i2c_transfer(p7mu_i2c->adapter, msg, ARRAY_SIZE(msg));
if (err == ARRAY_SIZE(msg))
return 0;
else if (err < 0)
return err;
else
return -EIO;
}
EXPORT_SYMBOL(p7mu_transfer);
int p7mu_mod16(u16 off, u16 val, u16 msk)
{
u16 v;
int const err = p7mu_read16(off, &v);
if (err)
return err;
if ((v & msk) == val)
return 0;
return p7mu_write16(off, (v & ~msk) | val);
}
EXPORT_SYMBOL(p7mu_mod16);
/********************************
* Interrupt controller handling
********************************/
static void p7mu_irq_mask(struct irq_data *d)
{
struct p7mu_chip *chip = irq_data_get_irq_chip_data(d);
chip->irq_mask &= ~(1 << (d->irq - chip->irq_base));
}
static void p7mu_irq_unmask(struct irq_data *d)
{
struct p7mu_chip *chip = irq_data_get_irq_chip_data(d);
chip->irq_mask |= 1 << (d->irq - chip->irq_base);
}
static void p7mu_irq_bus_lock(struct irq_data *d)
{
struct p7mu_chip *chip = irq_data_get_irq_chip_data(d);
mutex_lock(&chip->irq_lock);
}
static void p7mu_irq_bus_sync_unlock(struct irq_data *d)
{
struct p7mu_chip *chip = irq_data_get_irq_chip_data(d);
if (chip->irq_en != chip->irq_mask) {
//printk("irq sync %x %x\n", chip->irq_mask, chip->irq_en);
p7mu_write16(P7MU_PMC_INTERRUPT_EN_0_REG, chip->irq_mask & 0xffff);
p7mu_write16(P7MU_PMC_INTERRUPT_EN_1_REG, chip->irq_mask >> 16);
chip->irq_en = chip->irq_mask;
}
if (chip->gpio_ev_update != chip->gpio_ev) {
p7mu_write16(0x101, chip->gpio_ev_update);
chip->gpio_ev = chip->gpio_ev_update;
}
mutex_unlock(&chip->irq_lock);
}
static int p7mu_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
struct p7mu_chip *chip = irq_data_get_irq_chip_data(d);
int num = d->irq - chip->irq_base;
uint16_t level;
u16 p7mu_gpio_ev;
switch(num) {
case 3:
level = 0;
case 4:
level = 1;
case 5:
level = 2;
case 16:
level = 3;
case 17:
level = 4;
break;
default:
return 0;
};
p7mu_gpio_ev = chip->gpio_ev_update;
p7mu_gpio_ev &= ~(3 << (level*2));
if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
p7mu_gpio_ev |= 2 << (level*2);
else if (type & IRQ_TYPE_EDGE_FALLING)
p7mu_gpio_ev |= 3 << (level*2);
else if (type & IRQ_TYPE_EDGE_RISING)
return -EINVAL;
else if (type & IRQ_TYPE_LEVEL_HIGH)
p7mu_gpio_ev |= 0 << (level*2);
else if (type & IRQ_TYPE_LEVEL_LOW)
p7mu_gpio_ev |= 1 << (level*2);
chip->gpio_ev_update = p7mu_gpio_ev;
return 0;
}
static struct irq_chip p7mu_irq_chip = {
.name = "p7mu",
.irq_enable = p7mu_irq_unmask,
.irq_disable = p7mu_irq_mask,
.irq_bus_lock = p7mu_irq_bus_lock,
.irq_bus_sync_unlock = p7mu_irq_bus_sync_unlock,
.irq_set_type = p7mu_gpio_irq_set_type,
};
static int p7mu_setup_irq(struct p7mu_chip *chip)
{
int lvl;
const int irq_base = P7_IRQS;
const int irq_number = P7MU_GPIO_IRQS;
mutex_init(&chip->irq_lock);
chip->irq_base = irq_alloc_descs(-1, irq_base, irq_number, -1);
if (chip->irq_base < 0)
goto out_failed;
for (lvl = 0; lvl < irq_number; lvl++) {
int irq = lvl + chip->irq_base;
irq_clear_status_flags(irq, IRQ_NOREQUEST);
irq_set_chip_data(irq, chip);
irq_set_chip(irq, &p7mu_irq_chip);
irq_set_nested_thread(irq, true);
#ifdef CONFIG_ARM
set_irq_flags(irq, IRQF_VALID);
#else
irq_set_noprobe(irq);
#endif
}
p7mu_read16(0x101, &chip->gpio_ev);
chip->gpio_ev_update = chip->gpio_ev;
p7mu_rtc_res[0].start = p7mu_rtc_res[0].end += chip->irq_base;
dev_info(&p7mu_i2c->dev, "p7mu irq %d %d\n", chip->irq_base, chip->irq_base+irq_number);
return 0;
out_failed:
return -ENODEV;
}
static irqreturn_t p7mu_handle_irq(int irq, void* dev_id)
{
struct p7mu_chip *chip = dev_id;
u16 stat_raw[2];
u16 stat[2];
u16 status;
irqreturn_t ret = IRQ_HANDLED;
int tmp, tmp1;
tmp = p7mu_read16(P7MU_PMC_INTERRUPT_0_REG, &stat_raw[0]);
tmp1 = p7mu_read16(P7MU_PMC_INTERRUPT_1_REG, &stat_raw[1]);
if (tmp || tmp1) {
dev_warn(&p7mu_i2c->dev,
"failed to get interrupts status (%d %d)\n",
tmp, tmp1);
ret = IRQ_NONE;
}
if (stat_raw[0] == 0 && stat_raw[1] == 0) {
dev_warn(&p7mu_i2c->dev, "spurious interrupt ?\n");
ret = IRQ_NONE;
}
if (ret == IRQ_NONE)
return IRQ_NONE;
ret = IRQ_NONE;
/* XXX we could have race here. irq is triggered on p7mu, but while we are
handling p7mu irq (i2c sleep), we disable irq.
*/
/* Serve enabled interrutps only. */
stat[0] = chip->irq_en & stat_raw[0];
stat[1] = (chip->irq_en >> 16) & stat_raw[1];
pending_irq = 0;
for (tmp = 0; tmp < ARRAY_SIZE(p7mu_irqs); tmp++) {
struct p7mu_irq const* const i = &p7mu_irqs[tmp];
if (!(stat_raw[i->reg] & i->msk))
continue;
if (stat[i->reg] & i->msk) {
if (i->reg == 0 && (i->msk & DEFAULT_IRQ)) {
p7mu_read16(P7MU_PMC_POWER_SUPPLY_STATUS_REG, &power_supply_status);
p7mu_read16(P7MU_PMC_STATUS_REG, &status_register);
p7mu_read16(P7MU_PMC_TEMP_ERR_CNT_REG, &temperature_error_count);
if (i->msk == P7MU_INT_PWREN)
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_P7MU_PWREN);
else if (i->msk == P7MU_INT_REBOOT)
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_P7MU_REBOOT);
else if (i->msk == P7MU_INT_POWER) {
p7mu_write16(P7MU_APPL_DATA06_REG, power_supply_status);
p7mu_read16(P7MU_PMC_POWER_SUPPLY_FILTER_REG, &status);
p7mu_write16(P7MU_APPL_DATA07_REG, status);
p7mu_read16(P7MU_PMC_POWER_SUPPLY_MASK_REG, &status);
p7mu_write16(P7MU_APPL_DATA08_REG, status);
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_P7MU_POWER);
}
dev_warn(&p7mu_i2c->dev, "irq : %s\n", i->name);
pending_irq |= i->msk;
}
else
handle_nested_irq(chip->irq_base + tmp);
ret = IRQ_HANDLED;
}
else
dev_warn(&p7mu_i2c->dev, "spurious irq : %s\n", i->name);
}
/* Acknowledge interrupts all at once. */
tmp = p7mu_write16(P7MU_PMC_INTERRUPT_0_REG, ~stat_raw[0]);
tmp1 = p7mu_write16(P7MU_PMC_INTERRUPT_1_REG, ~stat_raw[1]);
if (tmp || tmp1) {
dev_warn(&p7mu_i2c->dev,
"failed to acknowledge interrupts (%d %d)\n",
tmp, tmp1);
}
if (pending_irq)
sysfs_notify(&p7mu_i2c->dev.kobj, NULL, "irq");
return ret;
}
static int __devinit p7mu_init_irqs(void)
{
struct p7mu_plat_data const* const pdata = p7mu_pdata();
int err;
struct p7mu_chip *chip = &p7mu_chip; //XXX
BUG_ON(! pdata);
/* Retrieve interrupt line the GPIO will act as. */
p7mu_i2c->irq = gpio_to_irq(pdata->gpio);
if (p7mu_i2c->irq < 0) {
/*
* GPIO platform data probably did not provide a
* suitable GPIO <-> IRQ mapping.
*/
dev_err(&p7mu_i2c->dev, "failed to find a valid GPIO interrupt\n");
return p7mu_i2c->irq;
}
/* Disable all interrupts before enabling GPIO pin as interrupt source. */
err = p7mu_write16(P7MU_PMC_INTERRUPT_EN_0_REG, 0);
if (err) {
dev_err(&p7mu_i2c->dev, "failed to disable interrupts\n");
return err;
}
err = p7mu_write16(P7MU_PMC_INTERRUPT_EN_1_REG, 0);
if (err) {
dev_err(&p7mu_i2c->dev, "failed to disable interrupts\n");
return err;
}
/* Setup GPIO for interrupt usage. */
err = gpio_request_one(pdata->gpio, GPIOF_IN, P7MU_DRV_NAME "_irq");
if (err) {
dev_err(&p7mu_i2c->dev, "failed to request GPIO interrupt\n");
return err;
}
/* Ack all interrupts if any: might be caused by calibration ?? */
p7mu_write16(P7MU_PMC_INTERRUPT_0_REG, 0);
p7mu_write16(P7MU_PMC_INTERRUPT_1_REG, 0);
/* Register base interrupt handler in threaded context. */
err = request_threaded_irq(p7mu_i2c->irq,
NULL,
&p7mu_handle_irq,
IRQF_TRIGGER_HIGH|IRQF_ONESHOT,
P7MU_DRV_NAME,
chip);
if (err) {
dev_err(&p7mu_i2c->dev, "failed to request base irq %d\n", p7mu_i2c->irq);
goto free_domain;
}
err = p7mu_setup_irq(chip);
if (err) {
goto free_irq;
}
/* Enable hardware interrupts handling. */
chip->irq_mask = chip->irq_en = DEFAULT_IRQ;
err = p7mu_write16(P7MU_PMC_INTERRUPT_EN_0_REG, chip->irq_mask);
if (!err)
err = p7mu_write16(P7MU_PMC_INTERRUPT_EN_1_REG, chip->irq_mask >> 16);
if (! err)
return 0;
free_irq:
dev_err(&p7mu_i2c->dev, "failed to enable interrupts\n");
free_irq(p7mu_i2c->irq, NULL);
free_domain:
/* Re-enable module once fixed. */
gpio_free(pdata->gpio);
return err;
}
static void p7mu_exit_irqs(void)
{
struct p7mu_plat_data const* const pdata = p7mu_pdata();
/* For sake of security: irqs should have been freed using p7mu_free_irq. */
p7mu_write16(P7MU_PMC_INTERRUPT_EN_0_REG, 0);
p7mu_write16(P7MU_PMC_INTERRUPT_EN_1_REG, 0);
irq_free_descs(p7mu_chip.irq_base, P7MU_GPIO_IRQS);
free_irq(p7mu_i2c->irq, NULL);
gpio_free(pdata->gpio);
}
int p7mu_gpio_to_irq(unsigned int gpio)
{
struct p7mu_chip *chip = &p7mu_chip; //XXX
int gpio_to_irq[] = {3, 4, 5, 16, 17};
if (gpio >= ARRAY_SIZE(gpio_to_irq))
return -EINVAL;
return gpio_to_irq[gpio] + chip->irq_base;
}
EXPORT_SYMBOL_GPL(p7mu_gpio_to_irq);
/***************************
* MFD sub-devices handling
***************************/
struct resource const* p7mu_request_region(struct platform_device* pdev,
struct resource const* res)
{
resource_size_t const start = res->start;
res = __request_region(&p7mu_iores,
start,
resource_size(res),
dev_name(&pdev->dev),
IORESOURCE_EXCLUSIVE);
if (res)
return res;
dev_warn(&pdev->dev, "failed to reserve I/O region 0x%04x\n", start);
return ERR_PTR(-EBUSY);
}
EXPORT_SYMBOL_GPL(p7mu_request_region);
void p7mu_release_region(struct resource const* res)
{
__release_region(&p7mu_iores, res->start, resource_size(res));
}
EXPORT_SYMBOL_GPL(p7mu_release_region);
static void (*p7mu_restart)(char, char const*);
static void p7mu_reboot(char mode, const char *cmd)
{
p7mu_write16(0xa00, 6);
p7mu_write16(0xb00, 6);
if (mode == 's') {
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_SOFT_REBOOT);
if (! p7mu_write16(P7MU_PMC_CMD_REG, P7MU_CMD_REBOOT))
mdelay(100);
dev_warn(&p7mu_i2c->dev, "soft reset failed, trying hard reset...\n");
}
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_HARD_REBOOT);
p7mu_restart(mode, cmd);
}
static void (*p7mu_pwroff)(void);
static void p7mu_shutdown(void)
{
int ret;
p7mu_write16(0xa00, 6);
p7mu_write16(0xb00, 6);
dev_err(&p7mu_i2c->dev, "p7mu power off\n");
ret = p7mu_write16(P7MU_PMC_CMD_REG, P7MU_CMD_SHUTDOWN);
/* wait power off : p7mu take 5s before going off ... */
if (ret == 0)
mdelay(6000);
dev_err(&p7mu_i2c->dev, "power off failed %d.\n", ret);
while (1)
cpu_do_idle();
}
/* We will store the jump address in the first 2 application regs */
int p7mu_save_cpu_jump(void *jump_addr)
{
u32 addr = virt_to_phys(jump_addr);
u16 addr_hi = (u16)(addr >> 16);
u16 addr_lo = (u16)(addr & 0xffff);
u16 val;
printk("p7mu_save_cpu_jump storing 0x%x\n", addr);
if ((0 != p7mu_write16(P7MU_APPL_DATA00_REG, addr_hi))
|| (0 != p7mu_write16(P7MU_APPL_DATA01_REG, addr_lo))) {
dev_err(&p7mu_i2c->dev, "cpu jump set fail.\n");
return -EIO;
}
if (p7mu_read16(P7MU_APPL_DATA00_REG, &val) || addr_hi != val) {
dev_err(&p7mu_i2c->dev, "DATA00_REG is wrong 0x%x\n", val);
return -EIO;
}
if (p7mu_read16(P7MU_APPL_DATA01_REG, &val) || addr_lo != val) {
dev_err(&p7mu_i2c->dev, "DATA01_REG is wrong 0x%x\n", val);
return -EIO;
}
return 0;
}
EXPORT_SYMBOL_GPL(p7mu_save_cpu_jump);
static void p7mu_wakeup_setup(void)
{
struct p7mu_plat_data const* const pdata = p7mu_pdata();
p7mu_write16(P7MU_PMC_GPIO_EVENT_SEL_REG, pdata->gpio_event_sel);
}
static void p7mu_print_idstr(char const* id, size_t size)
{
print_hex_dump(KERN_ERR,
"\t",
DUMP_PREFIX_NONE,
16,
1,
id,
size,
false);
}
static inline int p7mu_read_regs(u16 offset,
u16* buff,
size_t cnt)
{
/* there is no endian conversion on data ! */
return p7mu_transfer(cpu_to_be16(offset), buff, cnt, true);
}
static ssize_t show_boot_reason(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%s\n", boot_reasons_str[boot_reason]);
}
DEVICE_ATTR(boot_reason,0444,show_boot_reason,NULL);
static ssize_t show_irq_mask(int mask, char *buf)
{
int writeidx = 0;
int tmp;
for (tmp = 0; tmp < ARRAY_SIZE(p7mu_irqs); tmp++) {
struct p7mu_irq const* const i = &p7mu_irqs[tmp];
if (i->reg != 0 || !(i->msk & DEFAULT_IRQ))
continue;
if (mask & i->msk) {
writeidx += snprintf(buf+writeidx,PAGE_SIZE-writeidx,
"%s\n", i->name);
}
}
return writeidx;
}
static ssize_t show_irq(struct device *d, struct device_attribute *attr, char *buf)
{
int ret;
ret = show_irq_mask(pending_irq, buf);
pending_irq = 0;
return ret;
}
DEVICE_ATTR(irq,0444,show_irq,NULL);
static ssize_t show_irq_before_reboot(struct device *d, struct device_attribute *attr, char *buf)
{
int ret;
ret = show_irq_mask(irq_before_reboot, buf);
return ret;
}
DEVICE_ATTR(irq_before_reboot,0444,show_irq_before_reboot,NULL);
static ssize_t show_power_supply_status(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%d\n", power_supply_status);
}
DEVICE_ATTR(power_supply_status,0444,show_power_supply_status,NULL);
static ssize_t show_status_register(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%d\n", status_register);
}
DEVICE_ATTR(status_register,0444,show_status_register,NULL);
static ssize_t show_temperature_error_count(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%d\n", temperature_error_count);
}
DEVICE_ATTR(temperature_error_count,0444,show_temperature_error_count,NULL);
#ifdef CONFIG_SUSPEND
extern int p7_resume_cnt;
static long p7_resume_time;
static ssize_t show_resume_cnt(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%d", p7_resume_cnt);
}
DEVICE_ATTR(resume_cnt,0644,show_resume_cnt,NULL);
static ssize_t show_resume_time(struct device *d, struct device_attribute *attr, char *buf)
{
return snprintf(buf,PAGE_SIZE, "%ld", p7_resume_time);
}
DEVICE_ATTR(resume_time,0644,show_resume_time,NULL);
#endif
static struct attribute *p7mu_attrs[] = {
&dev_attr_boot_reason.attr,
&dev_attr_irq.attr,
&dev_attr_irq_before_reboot.attr,
&dev_attr_power_supply_status.attr,
&dev_attr_status_register.attr,
&dev_attr_temperature_error_count.attr,
#ifdef CONFIG_SUSPEND
&dev_attr_resume_cnt.attr,
&dev_attr_resume_time.attr,
#endif
NULL
};
static const struct attribute_group p7mu_attr_group = {
.attrs = p7mu_attrs,
};
static char const p7mu_idstr[] __devinitconst = { '\0', 'P', '7', 'M', 'U', '_', 'R' };
static int __devinit p7mu_probe_i2c(struct i2c_client* client,
struct i2c_device_id const* dev_id)
{
struct i2c_adapter* const adapt = client->adapter;
char id[4 * sizeof(u16)];
int vers;
int err;
u16 val;
u16 last_power_status[4];
/*
* For instance, depend on I2C driver supporting I2C_M_NOSTART so that
* we can aggregate multiple writes in a single transfer. We would
* otherwise need to allocate temporary buffers and perform an additional
* mem copy to serve client requests.
*/
if (! i2c_check_functionality(adapt, I2C_FUNC_PROTOCOL_MANGLING))
return -ENOTSUPP;
p7mu_i2c = client;
err = p7mu_read_regs(P7MU_CFG_ID, (u16*) id, 4);
if (err) {
dev_err(&client->dev, "failed to get id string\n");
goto err;
}
if (memcmp(p7mu_idstr, id, sizeof(p7mu_idstr))) {
/* XXX on some board the first read doesn't work, retry */
dev_err(&client->dev, "found invalid chip with wrong id string : retrying\n");
p7mu_print_idstr(id, ARRAY_SIZE(id));
p7mu_read_regs(P7MU_CFG_ID, (u16*) id, 4);
}
err = -ENODEV;
if (memcmp(p7mu_idstr, id, sizeof(p7mu_idstr))) {
dev_err(&client->dev, "found invalid chip with wrong id string\n");
p7mu_print_idstr(id, ARRAY_SIZE(id));
goto err;
}
vers = (int) id[7] - (int) '0';
if (vers < 0 || vers > 0xf) {
dev_err(&client->dev, "found invalid chip with wrong version string\n");
p7mu_print_idstr(id, ARRAY_SIZE(id));
goto err;
}
err = sysfs_create_group(&client->dev.kobj, &p7mu_attr_group);
if (err)
goto err;
p7mu_init_clk();
err = p7mu_read16(P7MU_APPL_DATA02_REG, &val);
if (err || val < P7MU_BOOT_REASON_FIRST_BOOT || val > P7MU_BOOT_REASON_OTHER)
{
dev_err(&client->dev, "failed to read boot reason\n");
boot_reason = P7MU_BOOT_REASON_FIRST_BOOT;
}
else
{
boot_reason = val;
dev_info(&client->dev, "boot reason: %s\n", boot_reasons_str[boot_reason]);
if (boot_reason == P7MU_BOOT_REASON_P7MU_POWER) {
p7mu_read16(P7MU_APPL_DATA06_REG, &last_power_status[0]);
p7mu_read16(P7MU_APPL_DATA07_REG, &last_power_status[1]);
p7mu_read16(P7MU_APPL_DATA08_REG, &last_power_status[2]);
dev_info(&client->dev, "satus before reboot %d, filter %04X, mask %04X\n",
last_power_status[0], last_power_status[1], last_power_status[2]);
}
}
err = p7mu_read16(0xf05, &val);
if (!err) {
dev_info(&client->dev, "p7mu event before reboot : %s%s%s%s%s\n",
(val & P7MU_INT_REBOOT) ? "pwren pulse,":"",
(val & P7MU_INT_POWER) ? "regulation failure,":"",
(val & P7MU_INT_TEMP_ERR) ? "temp error,":"",
(val & P7MU_INT_TEMP_WARN) ? "temp warn,":"",
(val & P7MU_INT_PWREN) ? "pwren failling,":""
);
irq_before_reboot = val;
}
// Set boot reason to other by default
p7mu_write16(P7MU_APPL_DATA02_REG, P7MU_BOOT_REASON_OTHER);
err = p7mu_init_irqs();
if (err) {
dev_err(&client->dev, "failed to initialize interrupts (%d)\n", err);
goto clk;
}
p7mu_pctl = pinctrl_get_select_default(&client->dev);
if (IS_ERR(p7mu_pctl)) {
dev_err(&client->dev, "failed to initialize pins (%ld)\n",
PTR_ERR(p7mu_pctl));
goto irq;
}
/* Now register cells (future platform_device) to MFD layer. */
err = mfd_add_devices(&client->dev,
0,
p7mu_cells,
ARRAY_SIZE(p7mu_cells),
NULL,
0);
if (err) {
dev_err(&client->dev, "failed to register sub-devices (%d)\n", err);
goto pin;
}
BUG_ON(! arm_pm_restart);
p7mu_restart = arm_pm_restart;
arm_pm_restart = p7mu_reboot;
p7mu_pwroff = pm_power_off;
pm_power_off = p7mu_shutdown;
p7mu_wakeup_setup();
dev_info(&client->dev, "attached device v%d to I2C bus %s.%d\n",
vers,
adapt->name,
adapt->nr);
return 0;
pin:
pinctrl_put(p7mu_pctl);
irq:
p7mu_exit_irqs();
clk:
p7mu_exit_clk();
sysfs_remove_group(&client->dev.kobj, &p7mu_attr_group);
err:
return err;
}
static int __devexit p7mu_remove_i2c(struct i2c_client* client)
{
struct i2c_adapter const* const adapt = client->adapter;
sysfs_remove_group(&client->dev.kobj, &p7mu_attr_group);
BUG_ON(! p7mu_restart);
arm_pm_restart = p7mu_restart;
BUG_ON(! pm_power_off);
pm_power_off = p7mu_pwroff;
mfd_remove_devices(&client->dev);
pinctrl_put(p7mu_pctl);
p7mu_exit_irqs();
p7mu_exit_clk();
dev_info(&adapt->dev, "detached from I2C bus %s.%d\n",
adapt->name,
adapt->nr);
return 0;
}
#ifdef CONFIG_SUSPEND
static int p7mu_suspend(struct i2c_client *client, pm_message_t mesg)
{
p7mu_write16(0xa00, 6);
p7mu_write16(0xb00, 6);
return 0;
}
static int p7mu_resume(struct i2c_client *client)
{
static int resume_cnt;
//struct p7mu_data *data = i2c_get_clientdata(client);
p7mu_resume_clk();
p7mu_write16(0xa00, 4);
p7mu_write16(0xb00, 4);
if (resume_cnt != p7_resume_cnt) {
struct timespec tv;
do_posix_clock_monotonic_gettime(&tv);
p7_resume_time = tv.tv_sec;
resume_cnt = p7_resume_cnt;
}
return 0;
}
#else
#define p7mu_suspend NULL
#define p7mu_resume NULL
#endif
static struct i2c_device_id const p7mu_i2c_id[] = {
{ P7MU_DRV_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, p7mu_i2c_id);
static struct i2c_driver p7mu_i2c_driver = {
.driver = {
.name = P7MU_DRV_NAME,
},
.probe = &p7mu_probe_i2c,
.remove = __devexit_p(&p7mu_remove_i2c),
.suspend = p7mu_suspend,
.resume = p7mu_resume,
.id_table = p7mu_i2c_id
};
static int __init p7mu_init_core(void)
{
return i2c_add_driver(&p7mu_i2c_driver);
}
postcore_initcall(p7mu_init_core);
static void __exit p7mu_exit_core(void)
{
i2c_del_driver(&p7mu_i2c_driver);
}
module_exit(p7mu_exit_core);
MODULE_DESCRIPTION("Parrot Power Management Unit core driver");
MODULE_AUTHOR("Grégor Boirie <gregor.boirie@parrot.com>");
MODULE_LICENSE("GPL");