blob: 59a9b32f22283de14dc932f86ae2643a7b9092da [file] [log] [blame]
/*
* gpio-axp288.c - AXP288 GPIO Driver
*
* Copyright (C) 2012, 2014 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Author: Iyer, Yegnesh S <yegnesh.s.iyer@intel.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/sched.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/gpio.h>
#define AXP288_GPIOIRQ 0x44
#define AXP288_GPIOIRQ_STAT 0x4C
#define AXP288_GPIO0CTL 0x90
#define AXP288_GPIO1CTL 0x92
#define AXP288_GPIO_SIGNAL 0x94
#define AXP288_GPIOIRQ_MASK 0x03
#define AXP288_GPIO_EDGE_BOTH_ENABLE 0xC0
#define AXP288_GPIO_EDGE_FALLING_ENABLE 0x40
#define AXP288_GPIO_EDGE_RISING_ENABLE 0x80
#define AXP288_GPIO_CTL_MASK 0xF8
#define AXP288_GPIO_OUT_DRIVE_MASK 0x01
#define AXP288_GPIO_CTL_INPUT 0x02
static struct pmic_gpio gpio_info;
static void __axp288_irq_mask(int gpio, int mask)
{
if (mask)
intel_soc_pmic_setb(AXP288_GPIOIRQ, 1 << gpio);
else
intel_soc_pmic_clearb(AXP288_GPIOIRQ, 1 << gpio);
}
static void __axp288_irq_type(int gpio, int type)
{
u8 ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
type &= IRQ_TYPE_EDGE_BOTH;
intel_soc_pmic_clearb(ctl, AXP288_GPIO_EDGE_BOTH_ENABLE);
if (type == IRQ_TYPE_EDGE_BOTH)
intel_soc_pmic_setb(ctl, AXP288_GPIO_EDGE_BOTH_ENABLE);
else if (type == IRQ_TYPE_EDGE_RISING)
intel_soc_pmic_setb(ctl, AXP288_GPIO_EDGE_RISING_ENABLE);
else if (type & IRQ_TYPE_EDGE_FALLING)
intel_soc_pmic_setb(ctl, AXP288_GPIO_EDGE_FALLING_ENABLE);
}
static int axp288_gpio_direction_input(struct gpio_chip *chip,
unsigned gpio)
{
u8 ctl;
u8 reg;
if (gpio > gpio_info.gpio_data->num_gpio)
return 0;
ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
reg = intel_soc_pmic_readb(ctl);
reg &= AXP288_GPIO_CTL_MASK;
reg |= AXP288_GPIO_CTL_INPUT;
intel_soc_pmic_writeb(ctl, reg);
return 0;
}
static int axp288_gpio_direction_output(struct gpio_chip *chip,
unsigned gpio, int value)
{
u8 ctl;
u8 reg;
if (gpio > gpio_info.gpio_data->num_gpio)
return 0;
ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
reg = intel_soc_pmic_readb(ctl);
reg &= AXP288_GPIO_CTL_MASK;
reg |= (value & AXP288_GPIO_OUT_DRIVE_MASK);
intel_soc_pmic_writeb(ctl, reg);
return 0;
}
static int axp288_gpio_get(struct gpio_chip *chip, unsigned gpio)
{
u8 ctl;
u8 reg;
u8 val;
if (gpio > gpio_info.gpio_data->num_gpio)
return 0;
ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
if (ctl & AXP288_GPIO_CTL_INPUT) {
val = intel_soc_pmic_readb(AXP288_GPIO_SIGNAL) & (1 << gpio);
return val >> gpio;
} else
return intel_soc_pmic_readb(ctl) & AXP288_GPIO_OUT_DRIVE_MASK;
}
static void axp288_gpio_set(struct gpio_chip *chip,
unsigned gpio, int value)
{
u8 ctl;
u8 reg;
if (gpio > gpio_info.gpio_data->num_gpio)
return;
ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
reg = intel_soc_pmic_readb(ctl);
reg &= AXP288_GPIO_CTL_MASK;
reg |= (value & AXP288_GPIO_OUT_DRIVE_MASK);
intel_soc_pmic_writeb(ctl, reg);
return;
}
static int axp288_irq_type(struct irq_data *data, unsigned type)
{
struct pmic_gpio *cg = irq_data_get_irq_chip_data(data);
cg->trigger_type = type;
cg->update |= UPDATE_TYPE;
return 0;
}
static int axp288_gpio_to_irq(struct gpio_chip *chip, unsigned gpio)
{
struct pmic_gpio *cg =
container_of(chip, struct pmic_gpio, chip);
if (gpio > cg->gpio_data->num_gpio)
return -1;
return cg->irq_base + gpio;
}
static void axp288_bus_lock(struct irq_data *data)
{
struct pmic_gpio *cg = irq_data_get_irq_chip_data(data);
mutex_lock(&cg->buslock);
}
static void axp288_bus_sync_unlock(struct irq_data *data)
{
struct pmic_gpio *cg = irq_data_get_irq_chip_data(data);
int gpio = data->irq - cg->irq_base;
if (cg->update & UPDATE_TYPE)
__axp288_irq_type(gpio, cg->trigger_type);
if (cg->update & UPDATE_MASK)
__axp288_irq_mask(gpio, cg->irq_mask);
cg->update = 0;
mutex_unlock(&cg->buslock);
}
static void axp288_irq_unmask(struct irq_data *data)
{
struct pmic_gpio *cg = irq_data_get_irq_chip_data(data);
cg->irq_mask = 0;
cg->update |= UPDATE_MASK;
}
static void axp288_irq_mask(struct irq_data *data)
{
struct pmic_gpio *cg = irq_data_get_irq_chip_data(data);
cg->irq_mask = 1;
cg->update |= UPDATE_MASK;
}
static struct irq_chip axp288_irqchip = {
.name = "PMIC-GPIO",
.irq_mask = axp288_irq_mask,
.irq_unmask = axp288_irq_unmask,
.irq_set_type = axp288_irq_type,
.irq_bus_lock = axp288_bus_lock,
.irq_bus_sync_unlock = axp288_bus_sync_unlock,
};
static irqreturn_t axp288_gpio_irq_handler(int irq, void *data)
{
struct pmic_gpio *cg = data;
int pending;
int gpio;
pending = intel_soc_pmic_readb(AXP288_GPIOIRQ_STAT) &
AXP288_GPIOIRQ_MASK;
intel_soc_pmic_setb(AXP288_GPIOIRQ_STAT,
pending & AXP288_GPIOIRQ_MASK);
local_irq_disable();
for (gpio = 0; gpio < cg->gpio_data->num_gpio; gpio++)
if (pending & (1 << gpio))
generic_handle_irq(cg->irq_base + gpio);
local_irq_enable();
return IRQ_HANDLED;
}
static void axp288_gpio_dbg_show(struct seq_file *s,
struct gpio_chip *chip)
{
int gpio;
u8 ctl;
u8 mask;
u8 irq;
u8 dir;
u8 value;
u8 reg;
irq = intel_soc_pmic_readb(AXP288_GPIOIRQ_STAT);
mask = intel_soc_pmic_readb(AXP288_GPIOIRQ);
for (gpio = 0; gpio < gpio_info.gpio_data->num_gpio; gpio++) {
ctl = gpio ? AXP288_GPIO1CTL : AXP288_GPIO0CTL;
reg = intel_soc_pmic_readb(ctl);
dir = (reg & AXP288_GPIO_CTL_INPUT);
if (dir)
value = (intel_soc_pmic_readb(AXP288_GPIO_SIGNAL) >>
gpio);
else
value = (reg & AXP288_GPIO_OUT_DRIVE_MASK);
seq_printf(s, " gpio-%-2d %s %s %s %s %s %s",
gpio, dir ? "in " : "out",
value ? "hi" : "lo",
ctl & AXP288_GPIO_EDGE_FALLING_ENABLE ?
"fall" : " ",
ctl & AXP288_GPIO_EDGE_RISING_ENABLE ?
"rise" : " ",
mask & (1 << gpio) ? "s0 mask " : "s0 unmask",
irq & (1 << gpio) ? "pending" : " ");
seq_printf(s, "\n");
}
}
static int axp288_gpio_probe(struct platform_device *pdev)
{
int irq = platform_get_irq(pdev, 0);
struct pmic_gpio *cg = &gpio_info;
int retval;
int i;
struct device *dev = intel_soc_pmic_dev();
struct pmic_gpio_data *gpio_data;
gpio_data = (struct pmic_gpio_data *)pdev->dev.platform_data;
mutex_init(&cg->buslock);
cg->chip.label = "axp288-gpio";
cg->chip.direction_input = axp288_gpio_direction_input;
cg->chip.direction_output = axp288_gpio_direction_output;
cg->chip.get = axp288_gpio_get;
cg->chip.set = axp288_gpio_set;
cg->chip.to_irq = axp288_gpio_to_irq;
cg->chip.base = -1;
cg->gpio_data = gpio_data;
cg->chip.ngpio = cg->gpio_data->num_vgpio;
cg->chip.can_sleep = 1;
cg->chip.dev = dev;
cg->chip.dbg_show = axp288_gpio_dbg_show;
retval = gpiochip_add(&cg->chip);
if (retval) {
dev_warn(&pdev->dev, "add gpio chip error: %d\n", retval);
return retval;
}
cg->irq_base = irq_alloc_descs(-1, INTEL_PMIC_IRQBASE,
cg->gpio_data->num_gpio, 0);
for (i = 0; i < cg->gpio_data->num_gpio; i++) {
irq_set_chip_data(i + cg->irq_base, cg);
irq_set_chip_and_handler_name(i + cg->irq_base,
&axp288_irqchip,
handle_simple_irq,
"demux");
}
retval = request_threaded_irq(irq, NULL, axp288_gpio_irq_handler,
IRQF_ONESHOT, "axp288_gpio", cg);
if (retval) {
dev_warn(&pdev->dev, "request irq failed: %d\n", retval);
return retval;
}
return 0;
}
static struct platform_device_id axp288_gpio_id_table[] = {
{ .name = "dollar_cove_gpio" },
{},
};
static struct platform_driver axp288_gpio_driver = {
.probe = axp288_gpio_probe,
.driver = {
.name = "axp288_gpio",
},
.id_table = axp288_gpio_id_table,
};
MODULE_DEVICE_TABLE(platform, axp288_gpio_id_table);
module_platform_driver(axp288_gpio_driver);
MODULE_AUTHOR("Iyer, Yegnesh S <yegnesh.s.iyer@intel.com>");
MODULE_DESCRIPTION("AXP288 GPIO Driver");
MODULE_LICENSE("GPL");