blob: e083128dd3734f86a8f799de54d29e8055f371f4 [file] [log] [blame]
/**
* linux/drivers/parrot/pinctrl/p7-pinctrl.c - Parrot7 pin controller driver
* implementation
*
* Copyright (C) 2012 Parrot S.A.
*
* author: Gregor Boirie <gregor.boirie@parrot.com>
* date: 01-Jun-2012
*
* This file is released under the GPL
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinmux.h>
#include "p7-pinctrl.h"
#include "../../pinctrl/core.h"
/*****************************************************************************
* Parrot7 chips pinctrl driver: implements I/O pin space handling.
*
* Pin / group mapping is particular here since our hardware allows
* us to multiplex every controllable pins using up to 4 mutually exclusive
* functions. There is no notion of per group multiplexing at hardware level.
* However, pinctrl subsystem requires driver to provide groups of pins to
* support I/O multiplexing.
* Hence, an identity mapping between hardware pin and corresponding fake
* software / logical group is carried out through the use of pinctrl_ops
* operation pointers.
* Hardware pin multiplexing is managed by assigning up to 4 pinmux functions
* for each logical group (i.e., per physical pin thanks to identity map).
*
* Althought I could have grouped pins in an arbitrary manner to reduce number
* of multiplexing entities, this would not fit current needs since we rely on
* a flexible way to support future boards (we have not heard of yet) without
* breaking existing platform dependent pinctrl code which instantiates
* pins / groups / functions.
* Moreover, I wanted to avoid misconfigurations between drivers and platform
* pinctrl code for IPs which have the ability to multiplex their own outputs
* to chip pins (like AAI, SPI...). It seems to me the simplest way to achieve
* this, is to enforce a direct group / pin mapping at pinctrl level and
* perform complex multiplexing inside the IP / driver (since it is needed any
* way).
****************************************************************************/
/* I/O remapped address of register space */
static u32 volatile __iomem* p7ctl_vaddr;
/* Physical address of register space */
static phys_addr_t p7ctl_paddr;
/* Size of register space */
static resource_size_t p7ctl_sz;
/* Pinctrl platform device */
static struct platform_device const* p7ctl_pdev;
/* Pinctrl device */
static struct pinctrl_dev* p7ctl_dev;
/* Platform specific data given at loading time */
static struct p7ctl_plat_data const* p7ctl_pdata;
#ifdef CONFIG_PM_SLEEP
/* Memory area to store registers */
static u32* p7ctl_saved_regs;
#endif
#ifdef DEBUG
#define P7CTL_ASSERT_PIN(_pin_id) \
BUG_ON(! p7ctl_pdata->pins); \
BUG_ON(! p7ctl_pdata->pins_nr); \
BUG_ON(_pin_id >= p7ctl_pdata->pins_nr); \
BUG_ON(p7ctl_pdata->pins[_pin_id].number != _pin_id); \
BUG_ON(! p7ctl_pdata->pins[_pin_id].name)
#else
#define P7CTL_ASSERT_PIN(_pin_id)
#endif
/************************
* "Dummy" functions handling
************************/
/* Not all functions exist on all chip revisions, to simplify things we just
* fill the blanks with "dummy" pins */
static inline int p7ctl_func_available(int func_id)
{
BUG_ON(func_id >= p7ctl_pdata->funcs_nr);
if (p7ctl_pdata->funcs[func_id].name != NULL) {
#ifdef DEBUG
BUG_ON(! p7ctl_pdata->funcs[func_id].name);
BUG_ON(p7ctl_pdata->funcs[func_id].mux_id >= 4);
BUG_ON(p7ctl_pdata->funcs[func_id].mux_id == 1);
P7CTL_ASSERT_PIN(p7ctl_pdata->funcs[func_id].pin_id);
#endif
return 1;
}
return 0;
}
/******************
* Fake pin groups
******************/
/* Does fake group exists (i.e., physical pin) ? */
static int p7ctl_list_grp(struct pinctrl_dev* pctl, unsigned int grp_id)
{
if (grp_id >= p7ctl_pdata->pins_nr)
return -EINVAL;
P7CTL_ASSERT_PIN(grp_id);
return 0;
}
/* Returns fake group name, i.e., physical pin name provided by platform. */
static char const* p7ctl_grp_name(struct pinctrl_dev* pctl, unsigned int grp_id)
{
if (grp_id >= p7ctl_pdata->pins_nr)
return NULL;
P7CTL_ASSERT_PIN(grp_id);
return p7ctl_pdata->pins[grp_id].name;
}
/* Return the single physical pin attached to fake group. */
static int p7ctl_grp_pins(struct pinctrl_dev* pctl,
unsigned int grp_id,
unsigned int const** pins,
unsigned int* pins_nr)
{
if (grp_id >= p7ctl_pdata->pins_nr)
return -EINVAL;
P7CTL_ASSERT_PIN(grp_id);
*pins = &p7ctl_pdata->pins[grp_id].number;
*pins_nr = 1;
return 0;
}
#ifdef CONFIG_DEBUG_FS
static void p7ctl_show_pin(struct pinctrl_dev* pctl,
struct seq_file* file,
unsigned pin_id)
{
seq_printf(file, " " P7CTL_DRV_NAME);
}
#else
#define p7ctl_show_pin NULL
#endif
static struct pinctrl_ops p7ctl_grp_ops = {
.list_groups = p7ctl_list_grp,
.get_group_name = p7ctl_grp_name,
.get_group_pins = p7ctl_grp_pins,
.pin_dbg_show = p7ctl_show_pin
};
/********************
* Pin configuration
********************/
static union p7ctl_setting p7ctl_cfg2set(unsigned long config)
{
return *((union p7ctl_setting*) (&config));
}
/* Check pin setting consistency. */
static int p7ctl_check_set(union p7ctl_setting settings)
{
/* Check pull up / down consistency. */
switch (settings.fields.pud) {
case P7CTL_PUD_HIGHZ:
case P7CTL_PUD_UP:
case P7CTL_PUD_DOWN:
case P7CTL_PUD_KEEP:
break;
default:
return -EINVAL;
}
/* Check slewrate consistency. */
if (settings.fields.slr > P7CTL_SLR_3)
return -EINVAL;
/* Check drive strength consistency. */
switch (settings.fields.drv) {
case P7CTL_DRV_TRI:
case P7CTL_DRV_0:
case P7CTL_DRV_1:
case P7CTL_DRV_2:
case P7CTL_DRV_3:
case P7CTL_DRV_4:
case P7CTL_DRV_5:
break;
default:
return -EINVAL;
}
return 0;
}
static int p7ctl_get_cfg(struct pinctrl_dev* pctl,
unsigned int pin_id,
unsigned long* config)
{
P7CTL_ASSERT_PIN(pin_id);
*config = (unsigned long) __raw_readl(p7ctl_vaddr + pin_id);
return 0;
}
static int p7ctl_set_cfg(struct pinctrl_dev* pctl,
unsigned int pin_id,
unsigned long config)
{
u32 volatile __iomem* const addr = p7ctl_vaddr + pin_id;
union p7ctl_setting cmd = { .word = __raw_readl(addr) };
union p7ctl_setting const set = p7ctl_cfg2set(config);
int const err = p7ctl_check_set(set);
P7CTL_ASSERT_PIN(pin_id);
if (err)
return err;
/* Setup Schmitt trigger. */
if (set.fields.set_smt)
cmd.fields.smt = set.fields.smt;
/* Setup pull up/down. */
if (set.fields.set_pud)
cmd.fields.pud = set.fields.pud;
/* Setup slew rate. */
if (set.fields.set_slr)
cmd.fields.slr = set.fields.slr;
/* Setup drive strength. */
if (set.fields.set_drv)
cmd.fields.drv = set.fields.drv;
__raw_writel(cmd.word, addr);
return 0;
}
#ifdef CONFIG_DEBUG_FS
static void p7ctl_show_pincfg(struct pinctrl_dev* pctl,
struct seq_file* file,
unsigned int pin_id)
{
union p7ctl_setting const set = { .word = __raw_readl(p7ctl_vaddr +
pin_id) };
P7CTL_ASSERT_PIN(pin_id);
#define ON_OFF(_b) ((_b) ? "on" : "off")
seq_printf(file, " schmitt(%s) pull_up(%s) pull_down(%s) bus_keeper(%s)",
ON_OFF(set.fields.smt),
ON_OFF(set.fields.pud & 1),
ON_OFF(set.fields.pud & 2),
ON_OFF(set.fields.pud & 4));
seq_printf(file, " slew_rate(%u)", set.fields.slr);
seq_printf(file, " drive_strength(%u)", set.fields.drv);
}
/*
* Callback for writes to the pincfg debugfs.
*
* buf is a null terminated string
*
* Format samples:
*
* echo '170:drive_strength(31)' > pinconf-pins
* echo '170:drive_strength:31' > pinconf-pins
* echo '170:pull_up:on' > pinconf-pins
*/
static int p7ctl_set_pincfg(struct pinctrl_dev* pctl, char *buf)
{
union p7ctl_setting set;
char *last;
char *function;
unsigned pin;
unsigned val;
pin = simple_strtoul(buf, &last, 0);
if (*last++ != ':')
return -EINVAL;
function = last;
for (;;) {
if (*last == '\0')
/* Unexpected end of string */
return -EINVAL;
if (*last == '(' || *last == ':') {
*last = '\0';
break;
}
last++;
}
last++;
switch (*last) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
val = simple_strtoul(last, &last, 0);
break;
case 'o':
/* Discriminate between "on" and "off" */
if (last[1] == 'n')
val = 1;
else if (last[1] == 'f')
val = 0;
else
return -EINVAL;
break;
default:
return -EINVAL;
}
if (pin >= p7ctl_pdata->pins_nr)
return -EINVAL;
if (strcmp(function, "drive_strength") == 0) {
set.fields.set_drv = 1;
if (val >= P7CTL_DRV_5)
val = P7CTL_DRV_5;
else if (val >= P7CTL_DRV_4)
val = P7CTL_DRV_4;
else if (val >= P7CTL_DRV_3)
val = P7CTL_DRV_3;
else if (val >= P7CTL_DRV_2)
val = P7CTL_DRV_2;
else if (val >= P7CTL_DRV_1)
val = P7CTL_DRV_1;
else if (val >= P7CTL_DRV_0)
val = P7CTL_DRV_0;
set.fields.drv = val;
} else if (strcmp(function, "schmitt") == 0) {
set.fields.set_smt = 1;
set.fields.smt = val;
} else if (strcmp(function, "pull_up") == 0) {
set.fields.set_pud = 1;
set.fields.pud = val ? P7CTL_PUD_UP : 0;
} else if (strcmp(function, "pull_down") == 0) {
set.fields.set_pud = 1;
set.fields.pud = val ? P7CTL_PUD_DOWN : 0;
} else if (strcmp(function, "bus_keeper") == 0) {
set.fields.set_pud = 1;
set.fields.pud = val ? P7CTL_PUD_KEEP : 0;
} else if (strcmp(function, "slew_rate") == 0) {
set.fields.set_slr = 1;
if (val >= P7CTL_SLR_3)
val = P7CTL_SLR_3;
else if (val >= P7CTL_SLR_2)
val = P7CTL_SLR_2;
else if (val >= P7CTL_SLR_1)
val = P7CTL_SLR_1;
else if (val >= P7CTL_SLR_0)
val = P7CTL_SLR_0;
set.fields.slr = val;
} else
return -EINVAL;
return p7ctl_set_cfg(pctl, pin, set.word);
}
#endif /* CONFIG_DEBUG_FS */
static struct pinconf_ops p7ctl_cfg_ops = {
.pin_config_get = p7ctl_get_cfg,
.pin_config_set = p7ctl_set_cfg,
#ifdef CONFIG_DEBUG_FS
.pin_config_dbg_show = p7ctl_show_pincfg,
.pin_config_dbg_set = p7ctl_set_pincfg,
.pin_config_group_dbg_show = p7ctl_show_pincfg
#endif /* CONFIG_DEBUG_FS */
};
/************************
* Multiplexing function
************************/
/* Does multiplexing function exists ? */
static int p7ctl_list_func(struct pinctrl_dev* pctl,
unsigned int func_id)
{
if (func_id >= p7ctl_pdata->funcs_nr)
return -EINVAL;
return 0;
}
/* Go and get function name provided by platform. */
static char const* p7ctl_func_name(struct pinctrl_dev* pctl,
unsigned int func_id)
{
if (func_id >= p7ctl_pdata->funcs_nr)
return NULL;
if (p7ctl_func_available(func_id))
return p7ctl_pdata->funcs[func_id].name;
return "NOT_AVAILABLE";
}
/* Go and get list of groups (i.e., physical pins) attached to function. */
static int p7ctl_func_grp(struct pinctrl_dev* pctl,
unsigned func_id,
char const* const** groups,
unsigned int* group_nr)
{
if (func_id >= p7ctl_pdata->funcs_nr)
return -EINVAL;
if (!p7ctl_func_available(func_id))
return -EINVAL;
*groups = &p7ctl_pdata->pins[p7ctl_pdata->funcs[func_id].pin_id].name;
*group_nr = 1;
return 0;
}
/* Enable physical pin and activate multiplexing function specified for it. */
static void p7ctl_enable_pin(unsigned int pin_id,
unsigned int mux_id)
{
u32 volatile __iomem* const addr = p7ctl_vaddr + pin_id;
union p7ctl_setting cmd = { .word = __raw_readl(addr) };
if (cmd.fields.ie) {
if (cmd.fields.func == mux_id)
return;
/* Disable pin at first. */
cmd.fields.ie = 0;
__raw_writel(cmd.word, addr);
}
/* Apply settings. */
cmd.fields.func = mux_id;
writel(cmd.word, addr);
/* Re-enable pin. */
cmd.fields.ie = 1;
writel(cmd.word, addr);
}
/* Enable physical pin and activate multiplexing function specified for it. */
static int p7ctl_enable_func(struct pinctrl_dev* pctl,
unsigned int func_id,
unsigned int grp_id)
{
struct p7ctl_function const* const func = &p7ctl_pdata->funcs[func_id];
if (!p7ctl_func_available(func_id))
return -EINVAL;
#ifdef DEBUG
BUG_ON(grp_id != func->pin_id);
#endif
p7ctl_enable_pin(grp_id, func->mux_id);
dev_dbg(&p7ctl_pdev->dev,
"enabled %s pin as %s\n",
p7ctl_pdata->pins[grp_id].name,
func->name);
return 0;
}
/* Disable physical pin. */
static void p7ctl_disable_pin(unsigned int pin_id)
{
u32 volatile __iomem* const addr = p7ctl_vaddr + pin_id;
union p7ctl_setting cmd;
cmd.word = __raw_readl(addr);
cmd.fields.ie = 0;
/* Setting ie to 0 just forces the input value reported by the pad to
* low. It still allows for the IP selected by the function to drive the
* PAD in output. There's no way to really "disable" the PAD. As a
* workaround, I switch back the PAD to function 1. It's either a GPIO
* (and unless it's buggy the GPIO controller should have unused PADs
* configured as inputs) or nothing. */
cmd.fields.func = 1;
__raw_writel(cmd.word, addr);
dev_dbg(&p7ctl_pdev->dev,
"disabled %s pin\n",
p7ctl_pdata->pins[pin_id].name);
}
/* Disable multiplexing function and turn off associated physical pin. */
static void p7ctl_disable_func(struct pinctrl_dev* pctl,
unsigned int func_id,
unsigned int grp_id)
{
#ifdef DEBUG
struct p7ctl_function const* const func = &p7ctl_pdata->funcs[func_id];
if (!p7ctl_func_available(func_id))
return;
BUG_ON(grp_id != func->pin_id);
#endif
p7ctl_disable_pin(grp_id);
}
static int p7ctl_request(struct pinctrl_dev* pctl, unsigned int pin)
{
struct pin_desc *desc;
desc = pin_desc_get(pctl, pin);
if (desc->gpio_owner)
return -EBUSY;
return 0;
}
#define CHAR_BIT 8
static int p7ctl_enable_gpio(struct pinctrl_dev* pctl,
struct pinctrl_gpio_range* range,
unsigned int gpio)
{
struct pin_desc *desc;
unsigned int const word = gpio / (sizeof(p7ctl_pdata->gpios[0]) *
CHAR_BIT);
#ifdef DEBUG
P7CTL_ASSERT_PIN(gpio);
BUG_ON(word >= p7ctl_pdata->gpios_nr);
#endif
desc = pin_desc_get(pctl, gpio);
if (desc->mux_owner)
return -EBUSY;
if (! (p7ctl_pdata->gpios[word] &
(1UL << (gpio % (sizeof(p7ctl_pdata->gpios[0]) * CHAR_BIT)))))
return -EINVAL;
p7ctl_enable_pin(gpio, 1);
dev_dbg(&p7ctl_pdev->dev,
"enabled %s pin as gpio %d\n",
p7ctl_pdata->pins[gpio].name,
gpio);
return 0;
}
static void p7ctl_disable_gpio(struct pinctrl_dev* pctl,
struct pinctrl_gpio_range* range,
unsigned int gpio)
{
unsigned int const word = gpio / (sizeof(p7ctl_pdata->gpios[0]) *
CHAR_BIT);
P7CTL_ASSERT_PIN(gpio);
BUG_ON(word >= p7ctl_pdata->gpios_nr);
if (! (p7ctl_pdata->gpios[word] &
(1UL << (gpio % (sizeof(p7ctl_pdata->gpios[0]) * CHAR_BIT))))) {
dev_warn(&p7ctl_pdev->dev,
"trying to disable non gpio %d pin %s\n",
gpio,
p7ctl_pdata->pins[gpio].name);
return;
}
p7ctl_disable_pin(gpio);
}
static struct pinmux_ops p7ctl_mux_ops = {
.request = p7ctl_request,
.list_functions = p7ctl_list_func,
.get_function_name = p7ctl_func_name,
.get_function_groups = p7ctl_func_grp,
.enable = p7ctl_enable_func,
.disable = p7ctl_disable_func,
.gpio_request_enable = p7ctl_enable_gpio,
.gpio_disable_free = p7ctl_disable_gpio
};
/*****************
* Pin controller
*****************/
static struct pinctrl_gpio_range p7ctl_gpio_range = {
.id = 0,
.base = 0,
.pin_base = 0
};
static struct pinctrl_desc p7ctl_desc = {
.name = P7CTL_DRV_NAME,
.pctlops = &p7ctl_grp_ops,
.pmxops = &p7ctl_mux_ops,
.confops = &p7ctl_cfg_ops,
.owner = THIS_MODULE
};
static int __devinit p7ctl_probe(struct platform_device* pdev)
{
int err;
char const* msg;
struct p7ctl_plat_data const* const pdata = dev_get_platdata(&pdev->dev);
struct resource const* const res = platform_get_resource(pdev,
IORESOURCE_MEM,
0);
#ifdef DEBUG
BUG_ON(! pdata);
BUG_ON(! pdata->funcs);
BUG_ON(! pdata->funcs_nr);
BUG_ON(! pdata->pins);
BUG_ON(! pdata->pins_nr);
BUG_ON(! pdata->gpios);
BUG_ON(! pdata->gpios_nr);
BUG_ON(pdata->gpios_nr !=
((pdata->pins_nr + (sizeof(p7ctl_pdata->gpios[0]) * CHAR_BIT) - 1) /
(sizeof(p7ctl_pdata->gpios[0]) * CHAR_BIT)));
BUG_ON(! pdata->gpio_drv);
BUG_ON(! res);
dev_dbg(&pdev->dev, "probing...\n");
#endif
#ifdef CONFIG_PM_SLEEP
p7ctl_saved_regs = kmalloc(pdata->pins_nr * sizeof(u32), GFP_KERNEL);
if (! p7ctl_saved_regs) {
dev_err(&pdev->dev, "Failed to allocate memory to save registers\n");
return -ENOMEM;
}
#endif
p7ctl_paddr = res->start;
p7ctl_sz = resource_size(res);
if (! request_mem_region(p7ctl_paddr,
p7ctl_sz,
dev_name(&pdev->dev))) {
msg = "failed to request memory";
err = -EBUSY;
goto err;
}
p7ctl_vaddr = ioremap(p7ctl_paddr, p7ctl_sz);
if (! p7ctl_vaddr) {
msg = "failed to remap memory";
err = -ENOMEM;
goto release;
}
p7ctl_pdev = pdev;
p7ctl_pdata = pdata;
p7ctl_desc.pins = pdata->pins,
p7ctl_desc.npins = pdata->pins_nr;
p7ctl_dev = pinctrl_register(&p7ctl_desc, &pdev->dev, NULL);
if (p7ctl_dev) {
p7ctl_gpio_range.npins = pdata->pins_nr;
p7ctl_gpio_range.name = pdata->gpio_drv;
pinctrl_add_gpio_range(p7ctl_dev, &p7ctl_gpio_range);
dev_info(&pdev->dev, "loaded\n");
return 0;
}
iounmap(p7ctl_vaddr);
err = -EINVAL;
msg = "failed to register pins controller";
release:
release_mem_region(p7ctl_paddr, p7ctl_sz);
err:
dev_err(&pdev->dev, "%s (%d)\n", msg, err);
return err;
}
static int __devexit p7ctl_remove(struct platform_device* pdev)
{
pinctrl_unregister(p7ctl_dev);
iounmap(p7ctl_vaddr);
release_mem_region(p7ctl_paddr, p7ctl_sz);
#ifdef CONFIG_PM_SLEEP
kfree(p7ctl_saved_regs);
#endif
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int p7ctl_suspend_noirq(struct device *dev)
{
memcpy_fromio(p7ctl_saved_regs, p7ctl_vaddr,
p7ctl_pdata->pins_nr * sizeof(u32));
return 0;
}
static int p7ctl_resume_noirq(struct device *dev)
{
memcpy_toio(p7ctl_vaddr, p7ctl_saved_regs,
p7ctl_pdata->pins_nr * sizeof(u32));
return 0;
}
#else
#define p7ctl_suspend_noirq NULL
#define p7ctl_resume_noirq NULL
#endif
static struct dev_pm_ops p7ctl_dev_pm_ops = {
.suspend_noirq = p7ctl_suspend_noirq,
.resume_noirq = p7ctl_resume_noirq,
};
static struct platform_driver p7ctl_driver = {
.driver = {
.name = P7CTL_DRV_NAME,
.owner = THIS_MODULE,
.pm = &p7ctl_dev_pm_ops,
},
.probe = &p7ctl_probe,
.remove = __devexit_p(&p7ctl_remove),
};
static int __init p7ctl_init(void)
{
return platform_driver_register(&p7ctl_driver);
}
postcore_initcall(p7ctl_init);
static void __exit p7ctl_exit(void)
{
platform_driver_unregister(&p7ctl_driver);
}
module_exit(p7ctl_exit);
MODULE_DESCRIPTION("Parrot7 I/O pins control driver");
MODULE_AUTHOR("Grégor Boirie <gregor.boirie@parrot.com>");
MODULE_LICENSE("GPL");