[PATCH] misc: add driver for Renesas R-Car Gyro-ADC/speed-pulse interfaces

From: Sergei Shtylyov
Date: Fri Jul 12 2013 - 19:51:54 EST


Add the driver for Gyro-ADC/speed-pulse interfaces found in Renesas R-Car SoCs.
Though being two separate devices, they have to be driven together because of
the shared start/stop register (located in Gyro-ADC still). At this time, only
speed-pulse interface is fully supported, the Gyro-ADC is just initialized and
started/stopped synchronously with the speed-pulse interface. A user interface
is implemented via several sysfs files which allow to read and reset the speed-
pulse interface's registers.

Signed-off-by: Sergei Shtylyov <sergei.shtylyov@xxxxxxxxxxxxxxxxxx>

---
This patch is againt the 'char-misc-next' barnch of Greg KH's 'char-misc.git'.

drivers/misc/Kconfig | 10 +
drivers/misc/Makefile | 1
drivers/misc/rcar-gyro-adc-speed-pulse.c | 231 +++++++++++++++++++++++++++++++
3 files changed, 242 insertions(+)

Index: char-misc/drivers/misc/Kconfig
===================================================================
--- char-misc.orig/drivers/misc/Kconfig
+++ char-misc/drivers/misc/Kconfig
@@ -528,6 +528,16 @@ config SRAM
the genalloc API. It is supposed to be used for small on-chip SRAM
areas found on many SoCs.

+config RCAR_GYRO_ADC_SPEED_PULSE
+ tristate "Renesas R-Car Gyro-ADC and speed-pulse interfaces driver"
+ depends on HAS_IOMEM
+ help
+ This driver allows you to read speed pulse signal characteristics via
+ sysfs. The Gyro-ADC interface is not currently supported.
+
+ This driver can also be built as a module. If so, the module
+ will be called rcar_gyro_adc_speed_pulse.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
Index: char-misc/drivers/misc/Makefile
===================================================================
--- char-misc.orig/drivers/misc/Makefile
+++ char-misc/drivers/misc/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_INTEL_MEI) += mei/
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o
obj-$(CONFIG_SRAM) += sram.o
+obj-$(CONFIG_RCAR_GYRO_ADC_SPEED_PULSE) += rcar-gyro-adc-speed-pulse.o
Index: char-misc/drivers/misc/rcar-gyro-adc-speed-pulse.c
===================================================================
--- /dev/null
+++ char-misc/drivers/misc/rcar-gyro-adc-speed-pulse.c
@@ -0,0 +1,231 @@
+/*
+ * Renesas R-Car Gyro-ADC and speed-pulse interfaces driver
+ *
+ * Copyright (C) 2013 Renesas Solutions Corp.
+ * Copyright (C) 2013 Cogent Embedded, Inc.
+ *
+ * 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; of the License.
+ *
+ * NOTE: Gyro-ADC interface is not really supported yet, just initialized
+ * and started/stopped synchronously with the speed-pulse interface.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/stat.h>
+#include <linux/types.h>
+
+#include <asm/div64.h>
+
+/* Gyro-ADC interface registers */
+#define ADC_MODE_SELECT 0x00
+#define ADC_SPEED_START_STOP 0x04
+#define ADC_CLOCK_LENGTH_COUNT 0x08
+#define _1_25_MS_LENGTH_COUNT 0x0C
+#define AD_CH_REAL_DATA(n) (0x10 + (n) * 4)
+#define AD_CH_ADD_DATA(n) (0x30 + (n) * 4)
+#define AD_CH_10MS_DATA_FIFO(n) (0x50 + (n) * 4)
+#define AD_FIFO_STATUS 0x70
+
+/* Speed-pulse interface registers */
+#define SPEED_PULSE_COUNT_DATA 0x000
+#define SPEED_PULSE_FILTER_SETTING 0x004
+#define SPEED_PULSE_COUNT_CLEARING 0x008
+#define SPEED_PULSE_100MS_LATCH_DATA 0x00C
+#define _100_MS_INT_COUNT 0x010
+#define INT_STATUS_AND_CLEAR 0x018
+#define _500_KHZ_FREQ_COUNT_SETTING 0x01C
+#define SPEED_PULSE_OFFSET_A 0x100
+#define SPEED_PULSE_OFFSET_B 0x104
+#define SPEED_PULSE_WIDTH 0x108
+#define SPEED_PULSE_OBSERVE_A 0x10C
+#define SPEED_PULSE_OBSERVE_B 0x110
+#define SPEED_PULSE_WIDTH_CLEARING 0x114
+#define SPEED_PULSE_WIDTH_TEST 0x118
+
+struct rcar_adc_sp_priv {
+ void __iomem *adc_base;
+ void __iomem *sp_base;
+ struct clk *adc_clk;
+ struct clk *sp_clk;
+};
+
+static u16 filter_time_const;
+module_param_named(filter, filter_time_const, ushort, S_IRUGO);
+MODULE_PARM_DESC(filter,
+ "Low-pass filter time constant in microseconds (default=0)");
+
+static ssize_t rcar_adc_sp_show_pulse_count(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rcar_adc_sp_priv *priv = dev_get_drvdata(dev);
+ u32 value = __raw_readl(priv->sp_base + SPEED_PULSE_COUNT_DATA);
+
+ return sprintf(buf, "%u\n", value);
+}
+
+#define BUILD_CLEAR(what, reg) \
+static ssize_t rcar_adc_sp_clear_##what(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct rcar_adc_sp_priv *priv = dev_get_drvdata(dev); \
+ u32 value; \
+ \
+ if (kstrtouint(buf, 10, &value) < 0) \
+ return -EINVAL; \
+ if (value > 1) \
+ return -EINVAL; \
+ \
+ __raw_writel(value, priv->sp_base + (reg)); \
+ \
+ return count; \
+}
+
+#define BUILD_SHOW(what, reg) \
+static ssize_t rcar_adc_sp_show_##what(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct rcar_adc_sp_priv *priv = dev_get_drvdata(dev); \
+ u32 value = __raw_readl(priv->sp_base + (reg)); \
+ \
+ return sprintf(buf, "%u\n", value * 2); /* in us */ \
+}
+
+BUILD_CLEAR(pulse_count, SPEED_PULSE_COUNT_CLEARING)
+BUILD_CLEAR(pulse_width, SPEED_PULSE_WIDTH_CLEARING)
+
+BUILD_SHOW(pulse_width, SPEED_PULSE_WIDTH)
+BUILD_SHOW(pulse_offset, SPEED_PULSE_OFFSET_A)
+BUILD_SHOW(pulse_observed, SPEED_PULSE_OBSERVE_A)
+
+static DEVICE_ATTR(pulse_count, S_IRUGO | S_IWUSR, rcar_adc_sp_show_pulse_count,
+ rcar_adc_sp_clear_pulse_count);
+static DEVICE_ATTR(pulse_width, S_IRUGO | S_IWUSR, rcar_adc_sp_show_pulse_width,
+ rcar_adc_sp_clear_pulse_width);
+static DEVICE_ATTR(pulse_offset, S_IRUGO, rcar_adc_sp_show_pulse_offset, NULL);
+static DEVICE_ATTR(pulse_observed, S_IRUGO, rcar_adc_sp_show_pulse_observed,
+ NULL);
+
+static struct attribute *rcar_adc_sp_attributes[] = {
+ &dev_attr_pulse_count.attr,
+ &dev_attr_pulse_width.attr,
+ &dev_attr_pulse_offset.attr,
+ &dev_attr_pulse_observed.attr,
+ NULL
+};
+
+static const struct attribute_group rcar_adc_sp_attr_group = {
+ .attrs = rcar_adc_sp_attributes,
+};
+
+static __init int rcar_adc_sp_probe(struct platform_device *pdev)
+{
+ struct rcar_adc_sp_priv *priv;
+ struct resource *res;
+ unsigned long freq;
+ u32 value;
+ u64 temp;
+ int err;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->adc_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->adc_base))
+ return PTR_ERR(priv->adc_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ priv->sp_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sp_base))
+ return PTR_ERR(priv->sp_base);
+
+ priv->adc_clk = devm_clk_get(&pdev->dev, "gyro-adc");
+ if (IS_ERR(priv->adc_clk))
+ return PTR_ERR(priv->adc_clk);
+
+ priv->sp_clk = devm_clk_get(&pdev->dev, "speed-pulse");
+ if (IS_ERR(priv->sp_clk))
+ return PTR_ERR(priv->sp_clk);
+
+ err = sysfs_create_group(&pdev->dev.kobj, &rcar_adc_sp_attr_group);
+ if (err)
+ return err;
+
+ clk_enable(priv->adc_clk);
+ clk_enable(priv->sp_clk);
+
+ /* Select mode 1 by default */
+ __raw_writel(0, priv->sp_base + ADC_MODE_SELECT);
+
+ freq = clk_get_rate(priv->adc_clk);
+
+ /* Select 10 us period for mode 1 */
+ value = DIV_ROUND_UP(freq * 10, 1000000);
+ if (value & 1)
+ value++;
+ __raw_writel(value, priv->sp_base + ADC_CLOCK_LENGTH_COUNT);
+
+ /* 1.25 ms period */
+ temp = freq * 1250ULL + 999999;
+ do_div(temp, 1000000);
+ value = (u32)temp;
+ __raw_writel(value, priv->sp_base + _1_25_MS_LENGTH_COUNT);
+
+ temp = freq * (u64)filter_time_const / 8 + 999999;
+ do_div(temp, 1000000);
+ value = (u32)temp;
+ __raw_writel(value, priv->sp_base + SPEED_PULSE_FILTER_SETTING);
+
+ freq = clk_get_rate(priv->sp_clk);
+
+ /* 2 us period */
+ value = DIV_ROUND_UP(freq * 2, 1000000);
+ __raw_writel(value, priv->sp_base + _500_KHZ_FREQ_COUNT_SETTING);
+
+ /* Start Gyro-ADC and speed-pulse interfaces */
+ __raw_writel(1, priv->adc_base + ADC_SPEED_START_STOP);
+
+ return 0;
+}
+
+static __exit int rcar_adc_sp_remove(struct platform_device *pdev)
+{
+ struct rcar_adc_sp_priv *priv = platform_get_drvdata(pdev);
+
+ /* Stop Gyro-ADC and speed-pulse interfaces */
+ __raw_writel(0, priv->adc_base + ADC_SPEED_START_STOP);
+
+ clk_disable(priv->sp_clk);
+ clk_disable(priv->adc_clk);
+
+ sysfs_remove_group(&pdev->dev.kobj, &rcar_adc_sp_attr_group);
+ return 0;
+}
+
+static struct platform_driver rcar_adc_sp_driver = {
+ .driver = {
+ .name = "rcar-gyro-adc-speed-pulse",
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(rcar_adc_sp__remove),
+};
+
+module_platform_driver_probe(rcar_adc_sp_driver, rcar_adc_sp_probe);
+
+MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@xxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Renesas R-Car Gyro-ADC and speed-pulse interfaces driver");
+MODULE_LICENSE("GPL v2");
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/