mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 09:02:00 +00:00
8eb12b9816
The driver can be used on either arm or arm64 platforms, but the latter doesn't have any platform-specific configuration options, so it must be possible to manually enable the driver. As the gpiolib is optional for arm64 arch, the gpio/led code must be compiled conditionally. Signed-off-by: Pawel Moll <pawel.moll@arm.com> Acked-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
529 lines
14 KiB
C
529 lines
14 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* Copyright (C) 2012 ARM Limited
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/io.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/vexpress.h>
|
|
|
|
#define SYS_ID 0x000
|
|
#define SYS_SW 0x004
|
|
#define SYS_LED 0x008
|
|
#define SYS_100HZ 0x024
|
|
#define SYS_FLAGS 0x030
|
|
#define SYS_FLAGSSET 0x030
|
|
#define SYS_FLAGSCLR 0x034
|
|
#define SYS_NVFLAGS 0x038
|
|
#define SYS_NVFLAGSSET 0x038
|
|
#define SYS_NVFLAGSCLR 0x03c
|
|
#define SYS_MCI 0x048
|
|
#define SYS_FLASH 0x04c
|
|
#define SYS_CFGSW 0x058
|
|
#define SYS_24MHZ 0x05c
|
|
#define SYS_MISC 0x060
|
|
#define SYS_DMA 0x064
|
|
#define SYS_PROCID0 0x084
|
|
#define SYS_PROCID1 0x088
|
|
#define SYS_CFGDATA 0x0a0
|
|
#define SYS_CFGCTRL 0x0a4
|
|
#define SYS_CFGSTAT 0x0a8
|
|
|
|
#define SYS_HBI_MASK 0xfff
|
|
#define SYS_ID_HBI_SHIFT 16
|
|
#define SYS_PROCIDx_HBI_SHIFT 0
|
|
|
|
#define SYS_LED_LED(n) (1 << (n))
|
|
|
|
#define SYS_MCI_CARDIN (1 << 0)
|
|
#define SYS_MCI_WPROT (1 << 1)
|
|
|
|
#define SYS_FLASH_WPn (1 << 0)
|
|
|
|
#define SYS_MISC_MASTERSITE (1 << 14)
|
|
|
|
#define SYS_CFGCTRL_START (1 << 31)
|
|
#define SYS_CFGCTRL_WRITE (1 << 30)
|
|
#define SYS_CFGCTRL_DCC(n) (((n) & 0xf) << 26)
|
|
#define SYS_CFGCTRL_FUNC(n) (((n) & 0x3f) << 20)
|
|
#define SYS_CFGCTRL_SITE(n) (((n) & 0x3) << 16)
|
|
#define SYS_CFGCTRL_POSITION(n) (((n) & 0xf) << 12)
|
|
#define SYS_CFGCTRL_DEVICE(n) (((n) & 0xfff) << 0)
|
|
|
|
#define SYS_CFGSTAT_ERR (1 << 1)
|
|
#define SYS_CFGSTAT_COMPLETE (1 << 0)
|
|
|
|
|
|
static void __iomem *vexpress_sysreg_base;
|
|
static struct device *vexpress_sysreg_dev;
|
|
static int vexpress_master_site;
|
|
|
|
|
|
void vexpress_flags_set(u32 data)
|
|
{
|
|
writel(~0, vexpress_sysreg_base + SYS_FLAGSCLR);
|
|
writel(data, vexpress_sysreg_base + SYS_FLAGSSET);
|
|
}
|
|
|
|
u32 vexpress_get_procid(int site)
|
|
{
|
|
if (site == VEXPRESS_SITE_MASTER)
|
|
site = vexpress_master_site;
|
|
|
|
return readl(vexpress_sysreg_base + (site == VEXPRESS_SITE_DB1 ?
|
|
SYS_PROCID0 : SYS_PROCID1));
|
|
}
|
|
|
|
u32 vexpress_get_hbi(int site)
|
|
{
|
|
u32 id;
|
|
|
|
switch (site) {
|
|
case VEXPRESS_SITE_MB:
|
|
id = readl(vexpress_sysreg_base + SYS_ID);
|
|
return (id >> SYS_ID_HBI_SHIFT) & SYS_HBI_MASK;
|
|
case VEXPRESS_SITE_MASTER:
|
|
case VEXPRESS_SITE_DB1:
|
|
case VEXPRESS_SITE_DB2:
|
|
id = vexpress_get_procid(site);
|
|
return (id >> SYS_PROCIDx_HBI_SHIFT) & SYS_HBI_MASK;
|
|
}
|
|
|
|
return ~0;
|
|
}
|
|
|
|
void __iomem *vexpress_get_24mhz_clock_base(void)
|
|
{
|
|
return vexpress_sysreg_base + SYS_24MHZ;
|
|
}
|
|
|
|
|
|
static void vexpress_sysreg_find_prop(struct device_node *node,
|
|
const char *name, u32 *val)
|
|
{
|
|
of_node_get(node);
|
|
while (node) {
|
|
if (of_property_read_u32(node, name, val) == 0) {
|
|
of_node_put(node);
|
|
return;
|
|
}
|
|
node = of_get_next_parent(node);
|
|
}
|
|
}
|
|
|
|
unsigned __vexpress_get_site(struct device *dev, struct device_node *node)
|
|
{
|
|
u32 site = 0;
|
|
|
|
WARN_ON(dev && node && dev->of_node != node);
|
|
if (dev && !node)
|
|
node = dev->of_node;
|
|
|
|
if (node) {
|
|
vexpress_sysreg_find_prop(node, "arm,vexpress,site", &site);
|
|
} else if (dev && dev->bus == &platform_bus_type) {
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
if (pdev->num_resources == 1 &&
|
|
pdev->resource[0].flags == IORESOURCE_BUS)
|
|
site = pdev->resource[0].start;
|
|
} else if (dev && strncmp(dev_name(dev), "ct:", 3) == 0) {
|
|
site = VEXPRESS_SITE_MASTER;
|
|
}
|
|
|
|
if (site == VEXPRESS_SITE_MASTER)
|
|
site = vexpress_master_site;
|
|
|
|
return site;
|
|
}
|
|
|
|
|
|
struct vexpress_sysreg_config_func {
|
|
u32 template;
|
|
u32 device;
|
|
};
|
|
|
|
static struct vexpress_config_bridge *vexpress_sysreg_config_bridge;
|
|
static struct timer_list vexpress_sysreg_config_timer;
|
|
static u32 *vexpress_sysreg_config_data;
|
|
static int vexpress_sysreg_config_tries;
|
|
|
|
static void *vexpress_sysreg_config_func_get(struct device *dev,
|
|
struct device_node *node)
|
|
{
|
|
struct vexpress_sysreg_config_func *config_func;
|
|
u32 site;
|
|
u32 position = 0;
|
|
u32 dcc = 0;
|
|
u32 func_device[2];
|
|
int err = -EFAULT;
|
|
|
|
if (node) {
|
|
of_node_get(node);
|
|
vexpress_sysreg_find_prop(node, "arm,vexpress,site", &site);
|
|
vexpress_sysreg_find_prop(node, "arm,vexpress,position",
|
|
&position);
|
|
vexpress_sysreg_find_prop(node, "arm,vexpress,dcc", &dcc);
|
|
err = of_property_read_u32_array(node,
|
|
"arm,vexpress-sysreg,func", func_device,
|
|
ARRAY_SIZE(func_device));
|
|
of_node_put(node);
|
|
} else if (dev && dev->bus == &platform_bus_type) {
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
if (pdev->num_resources == 1 &&
|
|
pdev->resource[0].flags == IORESOURCE_BUS) {
|
|
site = pdev->resource[0].start;
|
|
func_device[0] = pdev->resource[0].end;
|
|
func_device[1] = pdev->id;
|
|
err = 0;
|
|
}
|
|
}
|
|
if (err)
|
|
return NULL;
|
|
|
|
config_func = kzalloc(sizeof(*config_func), GFP_KERNEL);
|
|
if (!config_func)
|
|
return NULL;
|
|
|
|
config_func->template = SYS_CFGCTRL_DCC(dcc);
|
|
config_func->template |= SYS_CFGCTRL_FUNC(func_device[0]);
|
|
config_func->template |= SYS_CFGCTRL_SITE(site == VEXPRESS_SITE_MASTER ?
|
|
vexpress_master_site : site);
|
|
config_func->template |= SYS_CFGCTRL_POSITION(position);
|
|
config_func->device |= func_device[1];
|
|
|
|
dev_dbg(vexpress_sysreg_dev, "func 0x%p = 0x%x, %d\n", config_func,
|
|
config_func->template, config_func->device);
|
|
|
|
return config_func;
|
|
}
|
|
|
|
static void vexpress_sysreg_config_func_put(void *func)
|
|
{
|
|
kfree(func);
|
|
}
|
|
|
|
static int vexpress_sysreg_config_func_exec(void *func, int offset,
|
|
bool write, u32 *data)
|
|
{
|
|
int status;
|
|
struct vexpress_sysreg_config_func *config_func = func;
|
|
u32 command;
|
|
|
|
if (WARN_ON(!vexpress_sysreg_base))
|
|
return -ENOENT;
|
|
|
|
command = readl(vexpress_sysreg_base + SYS_CFGCTRL);
|
|
if (WARN_ON(command & SYS_CFGCTRL_START))
|
|
return -EBUSY;
|
|
|
|
command = SYS_CFGCTRL_START;
|
|
command |= write ? SYS_CFGCTRL_WRITE : 0;
|
|
command |= config_func->template;
|
|
command |= SYS_CFGCTRL_DEVICE(config_func->device + offset);
|
|
|
|
/* Use a canary for reads */
|
|
if (!write)
|
|
*data = 0xdeadbeef;
|
|
|
|
dev_dbg(vexpress_sysreg_dev, "command %x, data %x\n",
|
|
command, *data);
|
|
writel(*data, vexpress_sysreg_base + SYS_CFGDATA);
|
|
writel(0, vexpress_sysreg_base + SYS_CFGSTAT);
|
|
writel(command, vexpress_sysreg_base + SYS_CFGCTRL);
|
|
mb();
|
|
|
|
if (vexpress_sysreg_dev) {
|
|
/* Schedule completion check */
|
|
if (!write)
|
|
vexpress_sysreg_config_data = data;
|
|
vexpress_sysreg_config_tries = 100;
|
|
mod_timer(&vexpress_sysreg_config_timer,
|
|
jiffies + usecs_to_jiffies(100));
|
|
status = VEXPRESS_CONFIG_STATUS_WAIT;
|
|
} else {
|
|
/* Early execution, no timer available, have to spin */
|
|
u32 cfgstat;
|
|
|
|
do {
|
|
cpu_relax();
|
|
cfgstat = readl(vexpress_sysreg_base + SYS_CFGSTAT);
|
|
} while (!cfgstat);
|
|
|
|
if (!write && (cfgstat & SYS_CFGSTAT_COMPLETE))
|
|
*data = readl(vexpress_sysreg_base + SYS_CFGDATA);
|
|
status = VEXPRESS_CONFIG_STATUS_DONE;
|
|
|
|
if (cfgstat & SYS_CFGSTAT_ERR)
|
|
status = -EINVAL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
struct vexpress_config_bridge_info vexpress_sysreg_config_bridge_info = {
|
|
.name = "vexpress-sysreg",
|
|
.func_get = vexpress_sysreg_config_func_get,
|
|
.func_put = vexpress_sysreg_config_func_put,
|
|
.func_exec = vexpress_sysreg_config_func_exec,
|
|
};
|
|
|
|
static void vexpress_sysreg_config_complete(unsigned long data)
|
|
{
|
|
int status = VEXPRESS_CONFIG_STATUS_DONE;
|
|
u32 cfgstat = readl(vexpress_sysreg_base + SYS_CFGSTAT);
|
|
|
|
if (cfgstat & SYS_CFGSTAT_ERR)
|
|
status = -EINVAL;
|
|
if (!vexpress_sysreg_config_tries--)
|
|
status = -ETIMEDOUT;
|
|
|
|
if (status < 0) {
|
|
dev_err(vexpress_sysreg_dev, "error %d\n", status);
|
|
} else if (!(cfgstat & SYS_CFGSTAT_COMPLETE)) {
|
|
mod_timer(&vexpress_sysreg_config_timer,
|
|
jiffies + usecs_to_jiffies(50));
|
|
return;
|
|
}
|
|
|
|
if (vexpress_sysreg_config_data) {
|
|
*vexpress_sysreg_config_data = readl(vexpress_sysreg_base +
|
|
SYS_CFGDATA);
|
|
dev_dbg(vexpress_sysreg_dev, "read data %x\n",
|
|
*vexpress_sysreg_config_data);
|
|
vexpress_sysreg_config_data = NULL;
|
|
}
|
|
|
|
vexpress_config_complete(vexpress_sysreg_config_bridge, status);
|
|
}
|
|
|
|
|
|
void vexpress_sysreg_setup(struct device_node *node)
|
|
{
|
|
if (WARN_ON(!vexpress_sysreg_base))
|
|
return;
|
|
|
|
if (readl(vexpress_sysreg_base + SYS_MISC) & SYS_MISC_MASTERSITE)
|
|
vexpress_master_site = VEXPRESS_SITE_DB2;
|
|
else
|
|
vexpress_master_site = VEXPRESS_SITE_DB1;
|
|
|
|
vexpress_sysreg_config_bridge = vexpress_config_bridge_register(
|
|
node, &vexpress_sysreg_config_bridge_info);
|
|
WARN_ON(!vexpress_sysreg_config_bridge);
|
|
}
|
|
|
|
void __init vexpress_sysreg_early_init(void __iomem *base)
|
|
{
|
|
vexpress_sysreg_base = base;
|
|
vexpress_sysreg_setup(NULL);
|
|
}
|
|
|
|
void __init vexpress_sysreg_of_early_init(void)
|
|
{
|
|
struct device_node *node;
|
|
|
|
if (vexpress_sysreg_base)
|
|
return;
|
|
|
|
node = of_find_compatible_node(NULL, NULL, "arm,vexpress-sysreg");
|
|
if (node) {
|
|
vexpress_sysreg_base = of_iomap(node, 0);
|
|
vexpress_sysreg_setup(node);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
#define VEXPRESS_SYSREG_GPIO(_name, _reg, _value) \
|
|
[VEXPRESS_GPIO_##_name] = { \
|
|
.reg = _reg, \
|
|
.value = _reg##_##_value, \
|
|
}
|
|
|
|
static struct vexpress_sysreg_gpio {
|
|
unsigned long reg;
|
|
u32 value;
|
|
} vexpress_sysreg_gpios[] = {
|
|
VEXPRESS_SYSREG_GPIO(MMC_CARDIN, SYS_MCI, CARDIN),
|
|
VEXPRESS_SYSREG_GPIO(MMC_WPROT, SYS_MCI, WPROT),
|
|
VEXPRESS_SYSREG_GPIO(FLASH_WPn, SYS_FLASH, WPn),
|
|
VEXPRESS_SYSREG_GPIO(LED0, SYS_LED, LED(0)),
|
|
VEXPRESS_SYSREG_GPIO(LED1, SYS_LED, LED(1)),
|
|
VEXPRESS_SYSREG_GPIO(LED2, SYS_LED, LED(2)),
|
|
VEXPRESS_SYSREG_GPIO(LED3, SYS_LED, LED(3)),
|
|
VEXPRESS_SYSREG_GPIO(LED4, SYS_LED, LED(4)),
|
|
VEXPRESS_SYSREG_GPIO(LED5, SYS_LED, LED(5)),
|
|
VEXPRESS_SYSREG_GPIO(LED6, SYS_LED, LED(6)),
|
|
VEXPRESS_SYSREG_GPIO(LED7, SYS_LED, LED(7)),
|
|
};
|
|
|
|
static int vexpress_sysreg_gpio_direction_input(struct gpio_chip *chip,
|
|
unsigned offset)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int vexpress_sysreg_gpio_get(struct gpio_chip *chip,
|
|
unsigned offset)
|
|
{
|
|
struct vexpress_sysreg_gpio *gpio = &vexpress_sysreg_gpios[offset];
|
|
u32 reg_value = readl(vexpress_sysreg_base + gpio->reg);
|
|
|
|
return !!(reg_value & gpio->value);
|
|
}
|
|
|
|
static void vexpress_sysreg_gpio_set(struct gpio_chip *chip,
|
|
unsigned offset, int value)
|
|
{
|
|
struct vexpress_sysreg_gpio *gpio = &vexpress_sysreg_gpios[offset];
|
|
u32 reg_value = readl(vexpress_sysreg_base + gpio->reg);
|
|
|
|
if (value)
|
|
reg_value |= gpio->value;
|
|
else
|
|
reg_value &= ~gpio->value;
|
|
|
|
writel(reg_value, vexpress_sysreg_base + gpio->reg);
|
|
}
|
|
|
|
static int vexpress_sysreg_gpio_direction_output(struct gpio_chip *chip,
|
|
unsigned offset, int value)
|
|
{
|
|
vexpress_sysreg_gpio_set(chip, offset, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct gpio_chip vexpress_sysreg_gpio_chip = {
|
|
.label = "vexpress-sysreg",
|
|
.direction_input = vexpress_sysreg_gpio_direction_input,
|
|
.direction_output = vexpress_sysreg_gpio_direction_output,
|
|
.get = vexpress_sysreg_gpio_get,
|
|
.set = vexpress_sysreg_gpio_set,
|
|
.ngpio = ARRAY_SIZE(vexpress_sysreg_gpios),
|
|
.base = 0,
|
|
};
|
|
|
|
|
|
#define VEXPRESS_SYSREG_GREEN_LED(_name, _default_trigger, _gpio) \
|
|
{ \
|
|
.name = "v2m:green:"_name, \
|
|
.default_trigger = _default_trigger, \
|
|
.gpio = VEXPRESS_GPIO_##_gpio, \
|
|
}
|
|
|
|
struct gpio_led vexpress_sysreg_leds[] = {
|
|
VEXPRESS_SYSREG_GREEN_LED("user1", "heartbeat", LED0),
|
|
VEXPRESS_SYSREG_GREEN_LED("user2", "mmc0", LED1),
|
|
VEXPRESS_SYSREG_GREEN_LED("user3", "cpu0", LED2),
|
|
VEXPRESS_SYSREG_GREEN_LED("user4", "cpu1", LED3),
|
|
VEXPRESS_SYSREG_GREEN_LED("user5", "cpu2", LED4),
|
|
VEXPRESS_SYSREG_GREEN_LED("user6", "cpu3", LED5),
|
|
VEXPRESS_SYSREG_GREEN_LED("user7", "cpu4", LED6),
|
|
VEXPRESS_SYSREG_GREEN_LED("user8", "cpu5", LED7),
|
|
};
|
|
|
|
struct gpio_led_platform_data vexpress_sysreg_leds_pdata = {
|
|
.num_leds = ARRAY_SIZE(vexpress_sysreg_leds),
|
|
.leds = vexpress_sysreg_leds,
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
static ssize_t vexpress_sysreg_sys_id_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "0x%08x\n", readl(vexpress_sysreg_base + SYS_ID));
|
|
}
|
|
|
|
DEVICE_ATTR(sys_id, S_IRUGO, vexpress_sysreg_sys_id_show, NULL);
|
|
|
|
static int vexpress_sysreg_probe(struct platform_device *pdev)
|
|
{
|
|
int err;
|
|
struct resource *res = platform_get_resource(pdev,
|
|
IORESOURCE_MEM, 0);
|
|
|
|
if (!devm_request_mem_region(&pdev->dev, res->start,
|
|
resource_size(res), pdev->name)) {
|
|
dev_err(&pdev->dev, "Failed to request memory region!\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!vexpress_sysreg_base) {
|
|
vexpress_sysreg_base = devm_ioremap(&pdev->dev, res->start,
|
|
resource_size(res));
|
|
vexpress_sysreg_setup(pdev->dev.of_node);
|
|
}
|
|
|
|
if (!vexpress_sysreg_base) {
|
|
dev_err(&pdev->dev, "Failed to obtain base address!\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
setup_timer(&vexpress_sysreg_config_timer,
|
|
vexpress_sysreg_config_complete, 0);
|
|
|
|
vexpress_sysreg_dev = &pdev->dev;
|
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
vexpress_sysreg_gpio_chip.dev = &pdev->dev;
|
|
err = gpiochip_add(&vexpress_sysreg_gpio_chip);
|
|
if (err) {
|
|
vexpress_config_bridge_unregister(
|
|
vexpress_sysreg_config_bridge);
|
|
dev_err(&pdev->dev, "Failed to register GPIO chip! (%d)\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
platform_device_register_data(vexpress_sysreg_dev, "leds-gpio",
|
|
PLATFORM_DEVID_AUTO, &vexpress_sysreg_leds_pdata,
|
|
sizeof(vexpress_sysreg_leds_pdata));
|
|
#endif
|
|
|
|
device_create_file(vexpress_sysreg_dev, &dev_attr_sys_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id vexpress_sysreg_match[] = {
|
|
{ .compatible = "arm,vexpress-sysreg", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver vexpress_sysreg_driver = {
|
|
.driver = {
|
|
.name = "vexpress-sysreg",
|
|
.of_match_table = vexpress_sysreg_match,
|
|
},
|
|
.probe = vexpress_sysreg_probe,
|
|
};
|
|
|
|
static int __init vexpress_sysreg_init(void)
|
|
{
|
|
vexpress_sysreg_of_early_init();
|
|
return platform_driver_register(&vexpress_sysreg_driver);
|
|
}
|
|
core_initcall(vexpress_sysreg_init);
|