The Eberspaecher Flexcard PMC II offers a Flexray network synchronized counter with a selectable resolution of 1us, 100ns or 10ns. Add basic support for the timestamp counter with 1us resolution, which is the standard Flexray bus resolution. Signed-off-by: Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> Signed-off-by: Holger Dengler <dengler@xxxxxxxxxxxxx> cc: Arnd Bergmann <arnd@xxxxxxxx> cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> --- drivers/mfd/Kconfig | 1 + drivers/misc/Kconfig | 9 ++ drivers/misc/Makefile | 1 + drivers/misc/flexcard_posixclock.c | 240 +++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 drivers/misc/flexcard_posixclock.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 580f521..490b435 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -304,6 +304,7 @@ config MFD_FLEXCARD select MFD_CORE select IRQ_DOMAIN select FLEXCARD_MISC + select FLEXCARD_PCLOCK depends on PCI help This is the core driver for the Eberspaecher Flexcard diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3f54b58..658a6df 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -772,6 +772,15 @@ config FLEXCARD_MISC help Misc driver for Flexcard PMC II. +config FLEXCARD_PCLOCK + tristate "Flexcard posix clock Function driver" + depends on MFD_FLEXCARD + help + This is the posix clock function driver for the + Eberspaecher Flexcard PMC II carrier board. The + Flexcard provide a Flexray synchronized counter + configurable at 1, 10 or 100MHz. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 08a1729..df655bb 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o obj-$(CONFIG_FLEXCARD_MISC) += flexcard_misc.o +obj-$(CONFIG_FLEXCARD_PCLOCK) += flexcard_posixclock.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o diff --git a/drivers/misc/flexcard_posixclock.c b/drivers/misc/flexcard_posixclock.c new file mode 100644 index 0000000..146f0f3 --- /dev/null +++ b/drivers/misc/flexcard_posixclock.c @@ -0,0 +1,240 @@ +/* + * Eberspächer Flexcard PMC II - posix clock driver + * + * Copyright (c) 2014 - 2016, Linutronix GmbH + * Author: Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> + * Holger Dengler <dengler@xxxxxxxxxxxxx> + * + * 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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/posix-clock.h> +#include <linux/mfd/core.h> +#include <linux/mfd/flexcard.h> + +#define MAX_CLOCKS 16 +#define CLKSEL_OFF 0x10 + +#define FLEXCARD_RST_TS 0x8000 + +#define FLEXCARD_CLK_1MHZ 0 + +static dev_t flexcard_clk_devt; +static struct class *flexcard_clk_class; + +struct flexcard_clk { + struct posix_clock clock; + dev_t devid; + struct device *dev; + void __iomem *ts64; + void __iomem *reset; +}; + +static int flexcard_clk_getres(struct posix_clock *pc, struct timespec *tp) +{ + tp->tv_sec = 0; + tp->tv_nsec = 1000; + + return 0; +} + +static int flexcard_clk_gettime(struct posix_clock *pc, struct timespec *tp) +{ + struct flexcard_clk *clk = container_of(pc, struct flexcard_clk, clock); + u64 now; + u32 upper, rem; + +retry: + upper = readl(clk->ts64); + now = ((u64) upper << 32) | readl(clk->ts64 + 4); + if (upper != readl(clk->ts64)) + goto retry; + + tp->tv_sec = div_u64_rem(now, 1000000, &rem); + tp->tv_nsec = rem * 1000; + + return 0; +} + +static int flexcard_clk_settime(struct posix_clock *pc, + const struct timespec *tp) +{ + struct flexcard_clk *clk = container_of(pc, struct flexcard_clk, clock); + + /* The FlexCard posix clock could only be reset to 0 and not set */ + if (tp->tv_sec || tp->tv_nsec) + return -EINVAL; + + writel(FLEXCARD_RST_TS, clk->reset); + + return 0; +} + +static struct posix_clock_operations flexcard_clk_ops = { + .owner = THIS_MODULE, + .clock_getres = flexcard_clk_getres, + .clock_gettime = flexcard_clk_gettime, + .clock_settime = flexcard_clk_settime, +}; + +static int flexcard_clk_iomap(struct platform_device *pdev) +{ + struct flexcard_clk *clk = platform_get_drvdata(pdev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + clk->ts64 = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!clk->ts64) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENXIO; + + clk->reset = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!clk->reset) + return -ENOMEM; + + return 0; +} + +static int flexcard_clk_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell; + struct flexcard_clk *clk; + int major, ret; + + cell = mfd_get_cell(pdev); + if (!cell) + return -ENODEV; + + if (cell->id >= MAX_CLOCKS) { + dev_err(&pdev->dev, "all flexcard posix clocks in use: %d\n", + cell->id); + return -EBUSY; + } + + clk = devm_kzalloc(&pdev->dev, sizeof(*clk), GFP_KERNEL); + if (!clk) + return -ENOMEM; + + major = MAJOR(flexcard_clk_devt); + platform_set_drvdata(pdev, clk); + + ret = flexcard_clk_iomap(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to map resources: %d\n", ret); + goto out; + } + + clk->devid = MKDEV(major, cell->id); + clk->clock.ops = flexcard_clk_ops; + + writel(FLEXCARD_CLK_1MHZ, clk->ts64 + CLKSEL_OFF); + writel(FLEXCARD_RST_TS, clk->reset); + + clk->dev = device_create(flexcard_clk_class, &pdev->dev, clk->devid, + clk, "flexcard_clock%d", cell->id); + if (IS_ERR(clk->dev)) { + ret = PTR_ERR(clk->dev); + goto out; + } + + ret = posix_clock_register(&clk->clock, clk->devid); + if (ret) { + dev_err(&pdev->dev, + "failed to register flexcard posix clock: %d\n", ret); + goto out_destroy; + } + + dev_info(&pdev->dev, "flexcard posix clock %d registered", cell->id); + + return 0; + +out_destroy: + device_destroy(flexcard_clk_class, clk->devid); +out: + return ret; +} + +static int flexcard_clk_remove(struct platform_device *pdev) +{ + struct flexcard_clk *clk = platform_get_drvdata(pdev); + + posix_clock_unregister(&clk->clock); + device_destroy(flexcard_clk_class, clk->devid); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct platform_device_id flexcard_clk_id_table[] = { + { "flexcard-clock", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(platform, flexcard_clk_id_table); + +static struct platform_driver flexcard_clk_driver = { + .probe = flexcard_clk_probe, + .remove = flexcard_clk_remove, + .driver = { + .name = "flexcard-clock", + }, + .id_table = flexcard_clk_id_table, +}; + +static int __init flexcard_clk_init(void) +{ + int ret; + + flexcard_clk_class = class_create(THIS_MODULE, "flexcard_clock"); + if (IS_ERR(flexcard_clk_class)) { + pr_err("flexcard_clock: failed to allocate class\n"); + return PTR_ERR(flexcard_clk_class); + } + + ret = alloc_chrdev_region(&flexcard_clk_devt, 0, MAX_CLOCKS, + "flexcard_clock"); + if (ret < 0) { + pr_err("failed to allocate device region\n"); + goto out; + } + + ret = platform_driver_register(&flexcard_clk_driver); + if (ret < 0) + goto out_unregister; + + return 0; + +out_unregister: + unregister_chrdev_region(flexcard_clk_devt, MAX_CLOCKS); +out: + class_destroy(flexcard_clk_class); + + return ret; +} + +static void __exit flexcard_clk_exit(void) +{ + platform_driver_unregister(&flexcard_clk_driver); + unregister_chrdev_region(flexcard_clk_devt, MAX_CLOCKS); + class_destroy(flexcard_clk_class); +} + +module_init(flexcard_clk_init); +module_exit(flexcard_clk_exit); + +MODULE_AUTHOR("Holger Dengler <dengler@xxxxxxxxxxxxx>"); +MODULE_AUTHOR("Benedikt Spranger <b.spranger@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Eberspaecher Flexcard PMC II posix clock driver"); +MODULE_LICENSE("GPL v2"); -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe dmaengine" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html