>From 9b9cd7e0c4ab0dd6125845814996e2c984f29ef9 Mon Sep 17 00:00:00 2001 From: Jean-Nicolas Graux Date: Mon, 11 Feb 2013 10:22:36 +0100 Subject: [PATCH 1/2] ARM: ux500: introduce hardware observer platform driver Ux500 SoCs have 18 IOs dedicated to hardware signals monitoring. This platform device driver exposes a basic debugfs interface to easily configure the ux500 hardware observer pins. This code also replaces the quick and dirty "hw-observer-debug.c" file that was not pinctrl compatible. ST-Ericsson-ID: 478266 ST-Ericsson-FOSS-OUT-ID: Trivial Change-Id: I066a7e9907272c88a66747b881518040c0e1f842 Signed-off-by: Jean-Nicolas Graux --- arch/arm/mach-ux500/Kconfig | 8 + arch/arm/mach-ux500/Makefile | 1 + arch/arm/mach-ux500/ux500_hwobs.c | 838 +++++++++++++++++++++++++++++++++++++ arch/arm/mach-ux500/ux500_hwobs.h | 26 ++ 4 files changed, 873 insertions(+) create mode 100644 arch/arm/mach-ux500/ux500_hwobs.c create mode 100644 arch/arm/mach-ux500/ux500_hwobs.h diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index 31c5204..6d2f87b 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -318,6 +318,14 @@ config SBAG_TRACE help This option enables tracepoints for SBAG StBus Analyzer Generic +config UX500_HWOBS + bool "UX500 Hardware Observer" + depends on (DEBUG_FS || UX500_SOC_DB8500) + default n + help + This driver export debugfs entries to monitor + some UX500 SoC hardware signals. + source "arch/arm/mach-ux500/Kconfig-arch" source "arch/arm/mach-ux500/pm/Kconfig" source "arch/arm/mach-ux500/test/Kconfig" diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 8235800..75964b4 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -91,3 +91,4 @@ obj-$(CONFIG_SWITCH) += android_switch.o obj-$(CONFIG_INPUT_AB8505_MICRO_USB_DETECT) += ssg_micro_usb_iddet.o abx_micro_usb_iddet.o obj-$(CONFIG_SBAG) += sbag/ obj-$(CONFIG_CG1960_SPI) += board-cg1960-spi.o +obj-$(CONFIG_UX500_HWOBS) += ux500_hwobs.o diff --git a/arch/arm/mach-ux500/ux500_hwobs.c b/arch/arm/mach-ux500/ux500_hwobs.c new file mode 100644 index 0000000..d51f709 --- /dev/null +++ b/arch/arm/mach-ux500/ux500_hwobs.c @@ -0,0 +1,838 @@ +/* + * Copyright (C) ST-Ericsson SA 2013 + * Author: Jean-Nicolas Graux for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ux500_hwobs.h" + +#define PRCM_HWOBS_H (0x4B0) +#define PRCM_HWOBS_L (0x4B4) + +#define HW_OBS_EN (1<<4) +#define DDR1 (1<<8) + +#define HWOBS_CFG_MASK 3 + +#define NB_HWOBS 18 +#define HWOBS_IO(n, o, r) { .name = n, .offset = o, .reg = r} + +#define HWOBS_8540_GPIO_HOLE 2 +#define HWOBS_8540_NB_GPIOS_BEFORE_HOLE 14 + +enum hwobs_cfg { + HWOBS_CFG_MODEM = 0, + HWOBS_CFG_WAKE_UP, + HWOBS_CFG_DDR, + HWOBS_CFG_CLOCKING, + HWOBS_CFG_MAX, +}; + +static const char *hwobs_cfg_u8500[HWOBS_CFG_MAX] = { + [HWOBS_CFG_MODEM] = "modem", + [HWOBS_CFG_WAKE_UP] = "wake-up", + [HWOBS_CFG_DDR] = "ddr", + [HWOBS_CFG_CLOCKING] = "clocking", +}; + + +static const char *hwobs_cfg_u9540[HWOBS_CFG_MAX] = { + [HWOBS_CFG_MODEM] = "c2c", + [HWOBS_CFG_WAKE_UP] = "wake-up", + [HWOBS_CFG_DDR] = "ddr", + [HWOBS_CFG_CLOCKING] = "clocking", +}; + +static const char *hwobs_cfg_u8540[HWOBS_CFG_MAX] = { + [HWOBS_CFG_MODEM] = "xmip", + [HWOBS_CFG_WAKE_UP] = "wake-up", + [HWOBS_CFG_DDR] = "ddr", + [HWOBS_CFG_CLOCKING] = "clocking", +}; + +struct hwobs_io { + const char *name; + u32 offset; + u32 reg; +}; + +static const struct hwobs_io ux500_hwobs_ios[NB_HWOBS] = { + HWOBS_IO("mode_0", 0, PRCM_HWOBS_L), + HWOBS_IO("mode_1", 2, PRCM_HWOBS_L), + HWOBS_IO("mode_2", 4, PRCM_HWOBS_L), + HWOBS_IO("mode_3", 6, PRCM_HWOBS_L), + HWOBS_IO("mode_4", 8, PRCM_HWOBS_L), + HWOBS_IO("mode_5", 10, PRCM_HWOBS_L), + HWOBS_IO("mode_6", 12, PRCM_HWOBS_L), + HWOBS_IO("mode_7", 14, PRCM_HWOBS_L), + HWOBS_IO("mode_8", 16, PRCM_HWOBS_L), + HWOBS_IO("mode_9", 18, PRCM_HWOBS_L), + HWOBS_IO("mode_10", 20, PRCM_HWOBS_L), + HWOBS_IO("mode_11", 22, PRCM_HWOBS_L), + HWOBS_IO("mode_12", 24, PRCM_HWOBS_L), + HWOBS_IO("mode_13", 26, PRCM_HWOBS_L), + HWOBS_IO("mode_14", 28, PRCM_HWOBS_L), + HWOBS_IO("mode_15", 30, PRCM_HWOBS_L), + HWOBS_IO("mode_16", 0, PRCM_HWOBS_H), + HWOBS_IO("mode_17", 2, PRCM_HWOBS_H), +}; + +static const char *hwobs_ios_sel_u8500[NB_HWOBS][HWOBS_CFG_MAX] = { + [17] = { "modem_nPUreset", "IO force", "ape_min_bw[0]", "ULPPLLLOCK" }, + [16] = { "mod_sw reset req", "mailbox0", "ape_min_bw[1]", "DDRPLLLOCK" }, + [15] = { "mod_wake_req", "mailbox1", "ape_mem_req", "a9_wdrstreq_int[0]" }, + [14] = { "pwn_en", "mailbox2", "ape_mem_ack", "a9_wdrstreq_int[1]" }, + [13] = { "modpwrok", "mailbox3", "ape_mem_latency", "xp70 clk" }, + [12] = { "SW_reset_ack", "mailbox4", "reg_config_req[0]", "hwi2c_sda_out" }, + [11] = { "SW_reset_req", "mailbox5", "reg_config_req[1]", "ape_softrst" }, + [10] = { "", "mailbox6", "reg_config_ack[0]", "hwi2c_sda_in" }, + [9] = { "", "mailbox7", "reg_config_ack[1]", "hwi2c_scl" }, + [8] = { "mod_sys_clk_req", "RTT1wkup", "ddr_status[0]", "" }, + [7] = { "mod_sys_clk_ok", "RTT0wkup", "ddr_status[1]", "" }, + [6] = { "mod_state", "RTCwkup", "ddr_status[2]", "" }, + [5] = { "Wake host irq", "USB wkup", "ddr_status[3]", "" }, + [4] = { "corePD req", "SLIMBUS wkup", "mod_min_bw[0]", "" }, + [3] = { "Host port req", "fifo4500 it", "mod_min_bw[1]", "" }, + [2] = { "Host port available", "xp70 gpio it", "mod_mem_if_req", "" }, + [1] = { "Aux_clk_req", "A9_WFI(0)", "mod_mem_if_ack", "" }, + [0] = { "", "A9_WFI(1)", "mod_req_latency", "" }, +}; +static const char *hwobs_ios_sel_u9540[NB_HWOBS][HWOBS_CFG_MAX] = { + [17] = { "c2c_control[2]", "IO force", "ape_min_bw[0]", "ULPPLLLOCK" }, + [16] = { "c2c_ddr_latency", "mailbox0", "ape_min_bw[1]", "DDRPLLLOCK" }, + [15] = { "c2c_ddr_ack", "mailbox1", "ape_mem_req", "a9_wdrstreq_int[0]" }, + [14] = { "c2c_ddr_min_bw[1]", "mailbox2", "ape_mem_ack", "a9_wdrstreq_int[1]" }, + [13] = { "c2c_ddr_min_bw[0]", "mailbox3", "ape_mem_latency", "xp70 clk" }, + [12] = { "c2c_status[0]", "mailbox4", "reg_config_req[0]", "hwi2c_sda_out" }, + [11] = { "c2c_control[4]", "mailbox5", "reg_config_req[1]", "ape_softrst" }, + [10] = { "c2c_status[1]", "mailbox6", "reg_config_ack[0]", "hwi2c_sda_in" }, + [9] = { "c2c_status[2]", "mailbox7", "reg_config_ack[1]", "hwi2c_scl" }, + [8] = { "c2c_status[3]", "RTT1wkup", "ddr_status[0]", "DDR1PLLLOCK" }, + [7] = { "c2c_lp_interrupt[3]", "RTT0wkup", "ddr_status[1]", "" }, + [6] = { "c2c_lp_interrupt[2]", "RTCwkup", "ddr_status[2]", "" }, + [5] = { "c2c_lp_interrupt[1]", "USB wkup", "ddr_status[3]", "" }, + [4] = { "c2c_lp_interrupt[0]", "SLIMBUS wkup", "gfx_min_bw[0]", "" }, + [3] = { "c2c_reset_req", "fifo4500 it", "gfx_min_bw[1]", "" }, + [2] = { "c2c_irq[1]", "xp70 gpio it", "", "" }, + [1] = { "c2c_irq[0]", "A9_WFI(0)", "", "" }, + [0] = { "c2c_ddr_req", "A9_WFI(1)", "", "" }, +}; +static const char *hwobs_ios_sel_u8540[NB_HWOBS][HWOBS_CFG_MAX] = { + [17] = { "", "IO force", "ape_min_bw[0]", "ULPPLLLOCK" }, + [16] = { "xmip_ddr_latency", "mailbox0", "ape_min_bw[1]", "DDRPLLLOCK" }, + [15] = { "xmip_ddr_ack", "mailbox1", "ape_mem_req", "a9_wdrstreq_int[0]" }, + [14] = { "xmip_ddr_min_bw[1]", "mailbox2", "ape_mem_ack", "a9_wdrstreq_int[1]" }, + [13] = { "xmip_ddr_min_bw[0]", "mailbox3", "ape_mem_latency", "xp70 clk" }, + [12] = { "", "mailbox4", "reg_config_req[0]", "hwi2c_sda_out" }, + [11] = { "", "mailbox5", "reg_config_req[1]", "ape_softrst" }, + [10] = { "", "mailbox6", "reg_config_ack[0]", "hwi2c_sda_in" }, + [9] = { "", "mailbox7", "reg_config_ack[1]", "hwi2c_scl" }, + [8] = { "", "RTT1wkup", "ddr_status[0]", "DDR1PLLLOCK" }, + [7] = { "", "RTT0wkup", "ddr_status[1]", "" }, + [6] = { "", "RTCwkup", "ddr_status[2]", "" }, + [5] = { "", "USB wkup", "ddr_status[3]", "" }, + [4] = { "", "", "gfx_min_bw[0]", "" }, + [3] = { "", "fifo4500 it", "gfx_min_bw[1]", "" }, + [2] = { "", "xp70 gpio it", "", "" }, + [1] = { "", "A9_WFI(0)", "", "" }, + [0] = { "xmip_ddr_req", "A9_WFI(1)", "", "" }, +}; + +struct hwobs_soc_variant { + const char **cfg_names; + const struct hwobs_io *ios; +}; + +static const struct hwobs_soc_variant hwobs_variants[] = { + [HWOBS_SOC_8500] = { + .cfg_names = hwobs_cfg_u8500, + .ios = ux500_hwobs_ios, + }, + [HWOBS_SOC_9540] = { + .cfg_names = hwobs_cfg_u9540, + .ios = ux500_hwobs_ios, + }, + [HWOBS_SOC_8540] = { + .cfg_names = hwobs_cfg_u8540, + .ios = ux500_hwobs_ios, + } +}; + +struct hwobs_device { + struct mutex lock; + struct device *dev; + struct dentry *dir; + const struct hwobs_platform_data *pdata; + const struct hwobs_soc_variant *soc_variant; + void __iomem *reg_base; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; +}; + +static int hwobs_init_pinctrl(struct hwobs_device *hwobs) +{ + hwobs->pinctrl = devm_pinctrl_get(hwobs->dev); + if (IS_ERR(hwobs->pinctrl)) { + dev_err(hwobs->dev, "could not get hwobs pinctrl handle\n"); + return PTR_ERR(hwobs->pinctrl); + } + + hwobs->pins_default = pinctrl_lookup_state(hwobs->pinctrl, + PINCTRL_STATE_DEFAULT); + if (IS_ERR(hwobs->pins_default)) { + hwobs->pins_default = NULL; + dev_err(hwobs->dev, "could not lookup default state\n"); + return -EIO; + } + return 0; +} + +static void hwobs_destroy_pinctrl(struct hwobs_device *hwobs) +{ + if (hwobs->pinctrl) + devm_pinctrl_put(hwobs->pinctrl); + hwobs->pinctrl = NULL; + hwobs->pins_default = NULL; +} + +static inline bool hwobs_is_enabled(struct hwobs_device *hwobs) +{ + return readl(hwobs->reg_base + PRCM_HWOBS_H) & HW_OBS_EN; +} + +static int hwobs_enable(struct hwobs_device *hwobs) +{ + int retval; + + if (hwobs_is_enabled(hwobs)) + return 0; + + mutex_lock(&hwobs->lock); + /* + * Set HW_OBS_EN bit to enable HW_OBSERVER alternate functions + * that are activated and configured by the HW_OBS_SEL value. + */ + writel(readl(hwobs->reg_base + PRCM_HWOBS_H) | HW_OBS_EN, + hwobs->reg_base + PRCM_HWOBS_H); + + if (!hwobs->pinctrl) { + retval = hwobs_init_pinctrl(hwobs); + if (retval) { + mutex_unlock(&hwobs->lock); + return retval; + } + } + /* + * Select Hardware Observer alternate function in + * nomadik-gpio & prcm gpiocr. + */ + if (hwobs->pins_default) + retval = pinctrl_select_state(hwobs->pinctrl, + hwobs->pins_default); + + mutex_unlock(&hwobs->lock); + dev_dbg(hwobs->dev, "enabled\n"); + return retval; +} + +static int hwobs_disable(struct hwobs_device *hwobs) +{ + if (!hwobs_is_enabled(hwobs)) + return 0; + + mutex_lock(&hwobs->lock); + /* + * Unselect Hardware Observer alternate function in + * nomadik-gpio & prcm_gpiocr. + */ + hwobs_destroy_pinctrl(hwobs); + + /* Reset HW_OBS_EN bit */ + writel(readl(hwobs->reg_base + PRCM_HWOBS_H) & ~HW_OBS_EN, + hwobs->reg_base + PRCM_HWOBS_H); + + mutex_unlock(&hwobs->lock); + dev_dbg(hwobs->dev, "disabled\n"); + return 0; +} + +static inline bool hwobs_is_ddr1_enabled(struct hwobs_device *hwobs) +{ + return readl(hwobs->reg_base + PRCM_HWOBS_H) & DDR1; +} + +static void hwobs_ddr1_enable(struct hwobs_device *hwobs) +{ + mutex_lock(&hwobs->lock); + writel(readl(hwobs->reg_base + PRCM_HWOBS_H) | DDR1, + hwobs->reg_base + PRCM_HWOBS_H); + mutex_unlock(&hwobs->lock); +} + +static void hwobs_ddr1_disable(struct hwobs_device *hwobs) +{ + mutex_lock(&hwobs->lock); + writel(readl(hwobs->reg_base + PRCM_HWOBS_H) & ~DDR1, + hwobs->reg_base + PRCM_HWOBS_H); + mutex_unlock(&hwobs->lock); +} + +static int hwobs_io_set(struct hwobs_device *hwobs, int io_sel, + enum hwobs_cfg config) +{ + void __iomem *reg_addr; + u32 reg_val; + u32 offset; + + if (io_sel < 0 || io_sel >= NB_HWOBS) { + dev_err(hwobs->dev, "invalid io selection:%i\n", io_sel); + return -EIO; + } + + if (config >= HWOBS_CFG_MAX) { + dev_err(hwobs->dev, "invalid hwobs config:%i\n", config); + return -EIO; + } + + reg_addr = hwobs->reg_base + hwobs->soc_variant->ios[io_sel].reg; + offset = hwobs->soc_variant->ios[io_sel].offset; + mutex_lock(&hwobs->lock); + reg_val = readl(reg_addr); + reg_val &= ~(HWOBS_CFG_MASK << offset); + writel(reg_val | (config << offset), reg_addr); + mutex_unlock(&hwobs->lock); + return 0; +} + +static int hwobs_io_get(struct hwobs_device *hwobs, int io_sel) +{ + void __iomem *reg_addr; + u32 offset; + + if (io_sel < 0 || io_sel >= NB_HWOBS) { + dev_err(hwobs->dev, "invalid io selection:%i\n", io_sel); + return -EIO; + } + + reg_addr = hwobs->reg_base + hwobs->soc_variant->ios[io_sel].reg; + offset = hwobs->soc_variant->ios[io_sel].offset; + return (readl(reg_addr) >> offset) & HWOBS_CFG_MASK; +} + +static int hwobs_get_cfg_byname(struct hwobs_device *hwobs, const char *name) +{ + int i; + const char **cfg_names = hwobs->soc_variant->cfg_names; + + for (i = 0; i < HWOBS_CFG_MAX; i++) + if (cfg_names[i] && (!strncmp(name, cfg_names[i], + strlen(cfg_names[i])))) + return i; + return -EIO; +} + +static int hwobs_avmodes(struct seq_file *s, void *p) +{ + int i; + struct hwobs_device *hwobs = ((struct hwobs_device *)(s->private)); + const char **cfg_names = hwobs->soc_variant->cfg_names; + + for (i = 0; i < HWOBS_CFG_MAX; i++) + if (cfg_names[i] != NULL) + seq_printf(s, "%s\n", cfg_names[i]); + return 0; +} + +static int hwobs_ddr_read(struct seq_file *s, void *p) +{ + struct hwobs_device *hwobs = ((struct hwobs_device *)(s->private)); + seq_printf(s, "%i\n", hwobs_is_ddr1_enabled(hwobs)); + return 0; +} + +static ssize_t hwobs_ddr_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct hwobs_device *hwobs = + ((struct seq_file *)(file->private_data))->private; + int retval; + long int x; + + /* arg = 0/1 => disable/enable hwobs */ + retval = kstrtol_from_user(user_buf, count, 0, &x); + if (retval) + return retval; + if (x) + hwobs_ddr1_enable(hwobs); + else + hwobs_ddr1_disable(hwobs); + return count; +} + +static int hwobs_enable_read(struct seq_file *s, void *p) +{ + struct hwobs_device *hwobs = ((struct hwobs_device *)(s->private)); + seq_printf(s, "%i\n", hwobs_is_enabled(hwobs)); + return 0; +} + +static ssize_t hwobs_enable_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct hwobs_device *hwobs = + ((struct seq_file *)(file->private_data))->private; + int retval; + long int x; + + /* arg = 0/1 => disable/enable hwobs */ + retval = kstrtol_from_user(user_buf, count, 0, &x); + if (retval) + return retval; + if (x) + retval = hwobs_enable(hwobs); + else + retval = hwobs_disable(hwobs); + if (retval) + return retval; + return count; +} + +static int hwobs_gpio_x_read(struct seq_file *s, void *p) +{ + /* nothing to be done here! */ + return 0; +} + +static ssize_t hwobs_gpio_x_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct seq_file *s = (struct seq_file *)(file->private_data); + struct hwobs_device *hwobs = s->private; + char buf[32]; + char *token; + int bsize, retval, gpio; + long int x, output; + + if (hwobs_is_enabled(hwobs)) { + dev_warn(hwobs->dev, + "hardware observer is enabled, please disable it first.\n"); + return count; + } + + /* get userspace string and assure termination */ + bsize = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, bsize)) + return -EFAULT; + buf[bsize] = 0; + + /* find space delimiter between io number and hw obs mode */ + token = strchr(buf, ' '); + if (!token) { + dev_warn(hwobs->dev, + "gpio_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + + if (token == (buf + bsize - 1)) { + dev_warn(hwobs->dev, + "gpio_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + *token = '\0'; + retval = kstrtol(buf, 0, &x); + if (retval) { + dev_warn(hwobs->dev, + "gpio_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + if (x < 0 || x >= NB_HWOBS) { + dev_warn(hwobs->dev, "gpio_x: invalid io number: %li\n", x); + return count; + } + + /* jump over first token */ + token += 1; + retval = kstrtol(token, 0, &output); + if (retval) { + dev_warn(hwobs->dev, "gpio_x: invalid value: %s\n", token); + return count; + } + if (output < 0 || output > 1) { + dev_warn(hwobs->dev, "gpio_x: invalid value: %li\n", output); + return count; + } + + gpio = hwobs->pdata->gpio_base; + /* find absolute gpio number according to SoC variant */ + switch (hwobs->pdata->variant_id) { + case HWOBS_SOC_8500: + case HWOBS_SOC_9540: + gpio = hwobs->pdata->gpio_base + (NB_HWOBS - 1) - x; + break; + case HWOBS_SOC_8540: + /* take care of hole in the hardware observer io range */ + if (x < HWOBS_8540_NB_GPIOS_BEFORE_HOLE) + gpio = hwobs->pdata->gpio_base + + (NB_HWOBS - 1) + HWOBS_8540_GPIO_HOLE - x; + else + gpio = hwobs->pdata->gpio_base + + (NB_HWOBS - 1) - x; + break; + default: + break; + } + if (gpio_request(gpio, hwobs->dev->init_name) < 0) { + dev_warn(hwobs->dev, + "gpio_x: failed to request gpio %i\n", gpio); + return count; + } + if (gpio_direction_output(gpio, output) < 0) + dev_warn(hwobs->dev, "gpio_x: failed to set gpio %i to %li\n", + gpio, output); + dev_dbg(hwobs->dev, "gpio_x: gpio %i set to %li\n", gpio, output); + gpio_free(gpio); + return count; +} + +static int hwobs_mode_read(struct seq_file *s, void *p) +{ + /* nothing to be done here! */ + return 0; +} + +static ssize_t hwobs_mode_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct hwobs_device *hwobs = + ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int i, mode, bsize, retval; + + /* get userspace string and assure termination */ + bsize = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, bsize)) + return -EFAULT; + buf[bsize] = 0; + + retval = hwobs_get_cfg_byname(hwobs, buf); + if (retval < 0) + return retval; + mode = retval; + for (i = 0; i < NB_HWOBS; i++) { + retval = hwobs_io_set(hwobs, i, mode); + if (retval) + return retval; + } + return count; +} + +static int hwobs_mode_x_read(struct seq_file *s, void *p) +{ + /* nothing to be done here! */ + return 0; +} + +static ssize_t hwobs_mode_x_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct seq_file *s = (struct seq_file *)(file->private_data); + struct hwobs_device *hwobs = s->private; + char buf[32]; + char *token; + int mode, bsize, retval; + long int x; + + if (!hwobs_is_enabled(hwobs)) { + dev_warn(hwobs->dev, + "hardware observer is not available, please enable it first.\n"); + return count; + } + + /* get userspace string and assure termination */ + bsize = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, bsize)) + return -EFAULT; + buf[bsize] = 0; + + /* find space delimiter between io number and hw obs mode */ + token = strchr(buf, ' '); + if (!token) { + dev_warn(hwobs->dev, + "mode_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + if (token == (buf + bsize - 1)) { + dev_warn(hwobs->dev, + "mode_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + *token = '\0'; + retval = kstrtol(buf, 0, &x); + if (retval) { + dev_warn(hwobs->dev, + "mode_x: invalid parameter, read the \"readme\" file\n"); + return count; + } + if (x < 0 || x >= NB_HWOBS) { + dev_warn(hwobs->dev, "mode_x: invalid io number: %li\n", x); + return count; + } + + /* jump over first token */ + token += 1; + mode = hwobs_get_cfg_byname(hwobs, token); + if (mode < 0) { + dev_warn(hwobs->dev, "mode_x: invalid mode: %s\n", token); + return count; + } + retval = hwobs_io_set(hwobs, x, mode); + if (retval) + dev_warn(hwobs->dev, "mode_x: failed to set %s mode on io number %li\n", + token, x); + return count; +} + +static const char readme[] = + "\nHardware observer usage:\n\n" + "./available_modes: List supported hardware observer modes.\n" + "./ddr_select: Set/get current ddr instance (0|1) to monitor.\n" + "./enable: Enable/disable hardware observer.\n" + "./gpio_x: Configure hardware observer IO number x in gpio output:\n" + " Write IO number followed by 1 or 0 to select high/low output.\n" + " example: echo \"12 1\" > gpio_x\n" + "./mode: Force a mode to all hardware observer IOs.\n" + "./mode_x: Set mode to one harware observer IO:\n" + " Write IO number followed by mode to set current io mode:\n" + " example: echo \"12 wake-up\" > mode_x\n" + "./status: Show current mode of all hardware observer IOs.\n"; + +static int hwobs_readme(struct seq_file *s, void *p) +{ + return seq_printf(s, "%s", readme); +} + +static int hwobs_status(struct seq_file *s, void *p) +{ + int i; + int mode; + struct hwobs_device *hwobs = ((struct hwobs_device *)(s->private)); + const char **cfg_names = hwobs->soc_variant->cfg_names; + const struct hwobs_io *ios = hwobs->soc_variant->ios; + + if (!hwobs_is_enabled(hwobs)) + return seq_printf(s, + "hardware observer is not available, please enable it first.\n"); + + for (i = 0; i < NB_HWOBS; i++) { + mode = hwobs_io_get(hwobs, i); + switch (hwobs->pdata->variant_id) { + case HWOBS_SOC_8500: + seq_printf(s, "%8s: %10s (%s)\n", ios[i].name, + cfg_names[mode], hwobs_ios_sel_u8500[i][mode]); + break; + case HWOBS_SOC_9540: + seq_printf(s, "%8s: %10s (%s)\n", ios[i].name, + cfg_names[mode], hwobs_ios_sel_u9540[i][mode]); + break; + case HWOBS_SOC_8540: + seq_printf(s, "%8s: %10s (%s)\n", ios[i].name, + cfg_names[mode], hwobs_ios_sel_u8540[i][mode]); + break; + default: + break; + } + } + return 0; +} + +static int hwobs_avmodes_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_avmodes, inode->i_private); +} + +static int hwobs_ddr_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_ddr_read, inode->i_private); +} + +static int hwobs_enable_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_enable_read, inode->i_private); +} + +static int hwobs_gpio_x_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_gpio_x_read, inode->i_private); +} + +static int hwobs_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_mode_read, inode->i_private); +} + +static int hwobs_mode_x_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_mode_x_read, inode->i_private); +} + +static int hwobs_readme_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_readme, inode->i_private); +} + +static int hwobs_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, hwobs_status, inode->i_private); +} + +#define HWOBS_SEQF_FOPS(fops, op, wr) \ + static const struct file_operations fops = { \ + .open = op, \ + .write = wr, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + .owner = THIS_MODULE, \ + } + +HWOBS_SEQF_FOPS(hwobs_avmodes_fops, hwobs_avmodes_open, NULL); +HWOBS_SEQF_FOPS(hwobs_ddr_fops, hwobs_ddr_open, hwobs_ddr_write); +HWOBS_SEQF_FOPS(hwobs_enable_fops, hwobs_enable_open, hwobs_enable_write); +HWOBS_SEQF_FOPS(hwobs_gpio_x_fops, hwobs_gpio_x_open, hwobs_gpio_x_write); +HWOBS_SEQF_FOPS(hwobs_mode_fops, hwobs_mode_open, hwobs_mode_write); +HWOBS_SEQF_FOPS(hwobs_mode_x_fops, hwobs_mode_x_open, hwobs_mode_x_write); +HWOBS_SEQF_FOPS(hwobs_readme_fops, hwobs_readme_open, NULL); +HWOBS_SEQF_FOPS(hwobs_status_fops, hwobs_status_open, NULL); + +static int hwobs_debugfs_init(struct hwobs_device *hwobs) +{ + hwobs->dir = debugfs_create_dir("hardware_observer", NULL); + if (hwobs->dir == NULL) + goto fail; + + if (!debugfs_create_file("available_modes", S_IRUGO, + hwobs->dir, hwobs, &hwobs_avmodes_fops)) + goto fail; + if (!debugfs_create_file("ddr_select", S_IRUGO | S_IWUSR | S_IWGRP, + hwobs->dir, hwobs, &hwobs_ddr_fops)) + goto fail; + if (!debugfs_create_file("enable", S_IRUGO | S_IWUSR | S_IWGRP, + hwobs->dir, hwobs, &hwobs_enable_fops)) + goto fail; + if (!debugfs_create_file("gpio_x", S_IRUGO | S_IWUSR | S_IWGRP, + hwobs->dir, hwobs, &hwobs_gpio_x_fops)) + goto fail; + if (!debugfs_create_file("mode", S_IRUGO | S_IWUSR | S_IWGRP, + hwobs->dir, hwobs, &hwobs_mode_fops)) + goto fail; + if (!debugfs_create_file("mode_x", S_IRUGO | S_IWUSR | S_IWGRP, + hwobs->dir, hwobs, &hwobs_mode_x_fops)) + goto fail; + if (!debugfs_create_file("readme", S_IRUGO, + hwobs->dir, hwobs, &hwobs_readme_fops)) + goto fail; + if (!debugfs_create_file("status", S_IRUGO, + hwobs->dir, hwobs, &hwobs_status_fops)) + goto fail; + + dev_dbg(hwobs->dev, "debugfs initialized\n"); + return 0; + +fail: + if (hwobs->dir) { + debugfs_remove_recursive(hwobs->dir); + hwobs->dir = NULL; + } + dev_err(hwobs->dev, "debugfs entry failed\n"); + return -ENOMEM; +} + +static int __devinit hwobs_probe(struct platform_device *pdev) +{ + int retval = 0; + struct hwobs_device *hwobs; + struct hwobs_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "no configuration found\n"); + return -ENODEV; + } + hwobs = devm_kzalloc(&pdev->dev, sizeof(struct hwobs_device), + GFP_KERNEL); + if (!hwobs) + return -ENOMEM; + mutex_init(&hwobs->lock); + hwobs->dev = &pdev->dev; + hwobs->pdata = pdata; + + if (hwobs->pdata->variant_id >= HWOBS_SOC_MAX) { + dev_err(&pdev->dev, "invalid variant id:%u\n", + hwobs->pdata->variant_id); + return -ENODEV; + } + hwobs->soc_variant = &hwobs_variants[hwobs->pdata->variant_id]; + + retval = hwobs_init_pinctrl(hwobs); + if (retval) { + dev_err(&pdev->dev, "failed to get pin states\n"); + retval = -EIO; + goto err_pinctrl; + } + hwobs->reg_base = devm_ioremap_nocache(&pdev->dev, + pdata->regs_phys_base, SZ_4K); + if (hwobs->reg_base == NULL) { + retval = -EIO; + dev_err(&pdev->dev, "could not remap HWOBS registers\n"); + goto err_pinctrl; + } + + dev_set_drvdata(hwobs->dev, hwobs); + + retval = hwobs_debugfs_init(hwobs); + if (retval) + goto err_pinctrl; + + dev_info(&pdev->dev, "probed\n"); + return 0; + +err_pinctrl: + hwobs_destroy_pinctrl(hwobs); + return retval; +} + +static struct platform_driver hwobs_driver = { + .probe = hwobs_probe, + .remove = NULL, + .suspend = NULL, + .resume = NULL, + .driver = { + .name = HWOBS_DEV_NAME, + .owner = THIS_MODULE, + } +}; + +static int __init hwobs_init(void) +{ + return platform_driver_register(&hwobs_driver); +} + +static void __exit hwobs_exit(void) +{ + platform_driver_unregister(&hwobs_driver); +} + +module_init(hwobs_init); +module_exit(hwobs_exit); + +MODULE_AUTHOR("Jean-Nicolas Graux - ST-Ericsson"); +MODULE_DESCRIPTION("Hardware Observer Module driver"); +MODULE_ALIAS("hwobs"); diff --git a/arch/arm/mach-ux500/ux500_hwobs.h b/arch/arm/mach-ux500/ux500_hwobs.h new file mode 100644 index 0000000..b7642a1 --- /dev/null +++ b/arch/arm/mach-ux500/ux500_hwobs.h @@ -0,0 +1,26 @@ +#ifndef HWOBS_H +#define HWOBS_H + +#define HWOBS_DEV_NAME "hwobs" + +enum hwobs_soc_variants { + HWOBS_SOC_8500 = 0, + HWOBS_SOC_9540, + HWOBS_SOC_8540, + HWOBS_SOC_MAX +}; + +/** + * struct hwobs_platform_data - hardware observer platform device data struct. + * @variant_id: used to select one ux500 SoC variant. + * @regs_phys_base: the physical base address to access + * @hardware observer control registers. + * @gpio_base: the gpio base number of the hardware observers IOs. + */ +struct hwobs_platform_data { + u32 variant_id; + u32 regs_phys_base; + u32 gpio_base; +}; + +#endif -- 1.7.10