2009-01-08 09:49:34

by Davide Rizzo

[permalink] [raw]
Subject: [PATCH 1/3] Driver for user access to internal clocks

This driver is for user level programs to interact with system clocks.
It allows to read and modify rates and parents, using virtual files.
It requires the implementation of 2 additional functions in the clk interface:
clk_for_each() and clk_name().
Actually I implemented that functions only for Samsung S3C24xx platform.

Signed-off-by: Davide Rizzo <[email protected]>
---
diff -urNp linux-2.6.28/drivers/uio/clock.c
linux-2.6.28.elpa/drivers/uio/clock.c
--- linux-2.6.28/drivers/uio/clock.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.28.elpa/drivers/uio/clock.c 2009-01-07 18:52:09.000000000 +0100
@@ -0,0 +1,313 @@
+/*
+ driver/misc/clock.c
+
+ Written Feb 2008 by Davide Rizzo <[email protected]>
+
+ This driver allows to read and modify internal clocks' rates using
+ virtual files. User can also read and modify parents.
+
+ This driver requires implementation of clk_name() and clk_enum() functions
+ in architecture specific clock.c
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_info {
+ struct mutex mutex;
+ struct list_head head;
+ struct device *dev; /* here because needed by create_clock_attr */
+ int count;
+};
+
+struct attr {
+ struct list_head list;
+ struct device_attribute at;
+};
+
+static ssize_t clk_show(struct device *dev, struct device_attribute *attr,
+ char *buffer)
+{
+ unsigned long rate;
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk = clk_get(dev, attr->attr.name);
+
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ rate = 0;
+
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ rate = clk_get_rate(clk);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+
+ }
+ if (snprintf(buffer, PAGE_SIZE, "%ld\n", rate) > PAGE_SIZE)
+ buffer[PAGE_SIZE - 1] = '\0';
+ return strlen(buffer);
+}
+
+static ssize_t clk_store(struct device *dev, struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ unsigned long rate;
+ struct clk *clk;
+ int err = strict_strtoul(buffer, 10, &rate);
+
+ if (err)
+ return err;
+
+ clk = clk_get(dev, attr->attr.name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ mutex_lock(&info->mutex);
+
+ if (rate != 0) {
+ clk_set_rate(clk, clk_round_rate(clk, rate));
+ clk_enable(clk);
+ } else
+ clk_disable(clk);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+
+ return count;
+}
+
+static ssize_t clk_parent_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk, *parent;
+ char *s = kstrdup(attr->attr.name, GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ s[strlen(attr->attr.name) - 7] = '\0';
+ clk = clk_get(dev, s);
+ kfree(s);
+ buffer[0] = '\0';
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ parent = clk_get_parent(clk);
+ if (parent && !IS_ERR(parent)) {
+ const char *full_name = clk_get_name(parent);
+ if (IS_ERR(full_name)) {
+ mutex_unlock(&info->mutex);
+ return PTR_ERR(full_name);
+ }
+ strlcpy(buffer, full_name, PAGE_SIZE);
+ kfree(full_name);
+ }
+ strlcat(buffer, "\n", PAGE_SIZE);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+ }
+ return strlen(buffer);
+}
+
+static ssize_t clk_parent_store(struct device *dev,
+ struct device_attribute *attr, const char *buffer, size_t count)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk, *parent;
+ char *s;
+
+ parent = clk_get(dev, buffer);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+
+ s = kstrdup(attr->attr.name, GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ s[strlen(attr->attr.name) - 7] = '\0';
+ clk = clk_get(dev, s);
+ kfree(s);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ clk_set_parent(clk, parent);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+ }
+ clk_put(parent);
+ return count;
+}
+
+static int remove_driver(struct platform_device *pdev)
+{
+ struct clk_info *info = platform_get_drvdata(pdev);
+ struct attr *atp;
+
+ mutex_lock(&info->mutex);
+
+ /* Remove all virtual files */
+ while (!list_empty(&info->head)) {
+ atp = list_first_entry(&info->head, struct attr, list);
+ device_remove_file(&pdev->dev, &atp->at);
+ kfree(atp->at.attr.name);
+ list_del(&atp->list);
+ kfree(atp);
+ }
+ platform_set_drvdata(pdev, NULL);
+
+ mutex_unlock(&info->mutex);
+
+ kfree(info);
+ dev_notice(&pdev->dev, "removed\n");
+ return 0;
+}
+
+static int create_clock_attr(struct clk *clk, void *data)
+{
+ struct attr *atp;
+ int err, len;
+ struct clk_info *info = data;
+ const char *name = clk_get_name(clk);
+
+ /* Retrieve clock's name */
+ if (name == NULL || IS_ERR(name)) {
+ /* Don't return error, simply skip this one */
+ dev_err(info->dev, "invalid clock's name\n");
+ return PTR_ERR(name);
+ }
+
+ /* Create rate virtual file */
+ atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
+ if (!atp) {
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.attr.mode = S_IRUGO | S_IWUSR;
+ atp->at.attr.name = kstrdup(name, GFP_KERNEL);
+ if (!atp->at.attr.name) {
+ kfree(atp);
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.show = clk_show;
+ atp->at.store = clk_store;
+ err = device_create_file(info->dev, &atp->at);
+ if (err) {
+ kfree(atp->at.attr.name);
+ kfree(atp);
+ dev_err(info->dev, "failed to create file\n");
+ return err;
+ }
+ list_add(&atp->list, &info->head);
+
+ /* Create parent virtual file */
+ atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
+ if (!atp) {
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.attr.mode = S_IRUGO | S_IWUSR;
+ len = strlen(name) + 8;
+ atp->at.attr.name = kmalloc(len, GFP_KERNEL);
+ if (!atp->at.attr.name) {
+ kfree(atp);
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ strlcpy((char *)atp->at.attr.name, name, len);
+ strlcat((char *)atp->at.attr.name, "-parent", len);
+ atp->at.show = clk_parent_show;
+ atp->at.store = clk_parent_store;
+ err = device_create_file(info->dev, &atp->at);
+ if (err) {
+ kfree(atp->at.attr.name);
+ kfree(atp);
+ dev_err(info->dev, "failed to create file\n");
+ return err;
+ }
+ list_add(&atp->list, &info->head);
+
+ info->count++;
+ return 0;
+}
+
+static int probe_driver(struct platform_device *pdev)
+{
+ struct clk_info *info;
+ int err;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ mutex_init(&info->mutex);
+ INIT_LIST_HEAD(&info->head);
+ info->dev = &pdev->dev;
+ platform_set_drvdata(pdev, info);
+ err = clk_for_each(create_clock_attr, info);
+ if (err)
+ remove_driver(pdev);
+ else
+ dev_notice(&pdev->dev, "probed OK - %d clocks recognized\n",
+ info->count);
+ return err;
+}
+
+static struct platform_driver driver = {
+ .probe = probe_driver,
+ .remove = remove_driver,
+ .driver = {
+ .name = "clocks",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init clk_init_module(void)
+{
+ return platform_driver_register(&driver);
+}
+
+static void __exit clk_cleanup_module(void)
+{
+ platform_driver_unregister(&driver);
+}
+
+module_init(clk_init_module);
+module_exit(clk_cleanup_module);
+
+MODULE_AUTHOR("Davide Rizzo <[email protected]>");
+MODULE_DESCRIPTION("Clock driver");
+MODULE_SUPPORTED_DEVICE("clocks");
+MODULE_LICENSE("GPL");
diff -urNp linux-2.6.28/drivers/uio/Kconfig
linux-2.6.28.elpa/drivers/uio/Kconfig
--- linux-2.6.28/drivers/uio/Kconfig 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28.elpa/drivers/uio/Kconfig 2009-01-07 18:58:10.000000000 +0100
@@ -13,6 +13,18 @@ menuconfig UIO

if UIO

+
+config UIO_CLOCK
+ tristate "General purpose clock driver"
+ default y
+ ---help---
+ This driver allows to configure and control internal clocks'
+ rates and parents through virtual files.
+ This driver requires implementation of some additional functions
+ in architecture specific low-level drivers:
+ clk_for_each() and clk_get_name()
+ Currently they're implemented only on Samsung S3C24xx platforms.
+
config UIO_CIF
tristate "generic Hilscher CIF Card driver"
depends on PCI
diff -urNp linux-2.6.28/drivers/uio/Makefile
linux-2.6.28.elpa/drivers/uio/Makefile
--- linux-2.6.28/drivers/uio/Makefile 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28.elpa/drivers/uio/Makefile 2009-01-06 18:20:17.000000000 +0100
@@ -1,4 +1,5 @@
obj-$(CONFIG_UIO) += uio.o
+obj-$(CONFIG_UIO_CLOCK) += clock.o
obj-$(CONFIG_UIO_CIF) += uio_cif.o
obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o
obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o


2009-01-08 11:28:00

by Hans J. Koch

[permalink] [raw]
Subject: Re: [PATCH 1/3] Driver for user access to internal clocks

On Thu, Jan 08, 2009 at 10:49:12AM +0100, Davide Rizzo wrote:
> This driver is for user level programs to interact with system clocks.
> It allows to read and modify rates and parents, using virtual files.
> It requires the implementation of 2 additional functions in the clk interface:
> clk_for_each() and clk_name().
> Actually I implemented that functions only for Samsung S3C24xx platform.
>
> Signed-off-by: Davide Rizzo <[email protected]>
> ---
> diff -urNp linux-2.6.28/drivers/uio/clock.c
> linux-2.6.28.elpa/drivers/uio/clock.c
> --- linux-2.6.28/drivers/uio/clock.c 1970-01-01 01:00:00.000000000 +0100
> +++ linux-2.6.28.elpa/drivers/uio/clock.c 2009-01-07 18:52:09.000000000 +0100

Davide,
please don't put this in the drivers/uio/ directory. This is no UIO driver.

Thanks,
Hans


> @@ -0,0 +1,313 @@
> +/*
> + driver/misc/clock.c

Ah, it used to have a different place ;-)

> +
> + Written Feb 2008 by Davide Rizzo <[email protected]>
> +
> + This driver allows to read and modify internal clocks' rates using
> + virtual files. User can also read and modify parents.
> +
> + This driver requires implementation of clk_name() and clk_enum() functions
> + in architecture specific clock.c
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 2 of the License, or
> + (at your option) any later version.
> +
> + 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.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> +*/
> +
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +
> +struct clk_info {
> + struct mutex mutex;
> + struct list_head head;
> + struct device *dev; /* here because needed by create_clock_attr */
> + int count;
> +};
> +
> +struct attr {
> + struct list_head list;
> + struct device_attribute at;
> +};
> +
> +static ssize_t clk_show(struct device *dev, struct device_attribute *attr,
> + char *buffer)
> +{
> + unsigned long rate;
> + struct clk_info *info = dev_get_drvdata(dev);
> + struct clk *clk = clk_get(dev, attr->attr.name);
> +
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + rate = 0;
> +
> + if (!IS_ERR(clk)) {
> +
> + mutex_lock(&info->mutex);
> +
> + rate = clk_get_rate(clk);
> +
> + mutex_unlock(&info->mutex);
> +
> + clk_put(clk);
> +
> + }
> + if (snprintf(buffer, PAGE_SIZE, "%ld\n", rate) > PAGE_SIZE)
> + buffer[PAGE_SIZE - 1] = '\0';
> + return strlen(buffer);
> +}
> +
> +static ssize_t clk_store(struct device *dev, struct device_attribute *attr,
> + const char *buffer, size_t count)
> +{
> + struct clk_info *info = dev_get_drvdata(dev);
> + unsigned long rate;
> + struct clk *clk;
> + int err = strict_strtoul(buffer, 10, &rate);
> +
> + if (err)
> + return err;
> +
> + clk = clk_get(dev, attr->attr.name);
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + mutex_lock(&info->mutex);
> +
> + if (rate != 0) {
> + clk_set_rate(clk, clk_round_rate(clk, rate));
> + clk_enable(clk);
> + } else
> + clk_disable(clk);
> +
> + mutex_unlock(&info->mutex);
> +
> + clk_put(clk);
> +
> + return count;
> +}
> +
> +static ssize_t clk_parent_show(struct device *dev,
> + struct device_attribute *attr, char *buffer)
> +{
> + struct clk_info *info = dev_get_drvdata(dev);
> + struct clk *clk, *parent;
> + char *s = kstrdup(attr->attr.name, GFP_KERNEL);
> + if (!s)
> + return -ENOMEM;
> + s[strlen(attr->attr.name) - 7] = '\0';
> + clk = clk_get(dev, s);
> + kfree(s);
> + buffer[0] = '\0';
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> + if (!IS_ERR(clk)) {
> +
> + mutex_lock(&info->mutex);
> +
> + parent = clk_get_parent(clk);
> + if (parent && !IS_ERR(parent)) {
> + const char *full_name = clk_get_name(parent);
> + if (IS_ERR(full_name)) {
> + mutex_unlock(&info->mutex);
> + return PTR_ERR(full_name);
> + }
> + strlcpy(buffer, full_name, PAGE_SIZE);
> + kfree(full_name);
> + }
> + strlcat(buffer, "\n", PAGE_SIZE);
> +
> + mutex_unlock(&info->mutex);
> +
> + clk_put(clk);
> + }
> + return strlen(buffer);
> +}
> +
> +static ssize_t clk_parent_store(struct device *dev,
> + struct device_attribute *attr, const char *buffer, size_t count)
> +{
> + struct clk_info *info = dev_get_drvdata(dev);
> + struct clk *clk, *parent;
> + char *s;
> +
> + parent = clk_get(dev, buffer);
> + if (IS_ERR(parent))
> + return PTR_ERR(parent);
> +
> + s = kstrdup(attr->attr.name, GFP_KERNEL);
> + if (!s)
> + return -ENOMEM;
> + s[strlen(attr->attr.name) - 7] = '\0';
> + clk = clk_get(dev, s);
> + kfree(s);
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + if (!IS_ERR(clk)) {
> +
> + mutex_lock(&info->mutex);
> +
> + clk_set_parent(clk, parent);
> +
> + mutex_unlock(&info->mutex);
> +
> + clk_put(clk);
> + }
> + clk_put(parent);
> + return count;
> +}
> +
> +static int remove_driver(struct platform_device *pdev)
> +{
> + struct clk_info *info = platform_get_drvdata(pdev);
> + struct attr *atp;
> +
> + mutex_lock(&info->mutex);
> +
> + /* Remove all virtual files */
> + while (!list_empty(&info->head)) {
> + atp = list_first_entry(&info->head, struct attr, list);
> + device_remove_file(&pdev->dev, &atp->at);
> + kfree(atp->at.attr.name);
> + list_del(&atp->list);
> + kfree(atp);
> + }
> + platform_set_drvdata(pdev, NULL);
> +
> + mutex_unlock(&info->mutex);
> +
> + kfree(info);
> + dev_notice(&pdev->dev, "removed\n");
> + return 0;
> +}
> +
> +static int create_clock_attr(struct clk *clk, void *data)
> +{
> + struct attr *atp;
> + int err, len;
> + struct clk_info *info = data;
> + const char *name = clk_get_name(clk);
> +
> + /* Retrieve clock's name */
> + if (name == NULL || IS_ERR(name)) {
> + /* Don't return error, simply skip this one */
> + dev_err(info->dev, "invalid clock's name\n");
> + return PTR_ERR(name);
> + }
> +
> + /* Create rate virtual file */
> + atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
> + if (!atp) {
> + dev_err(info->dev, "out of kernel memory\n");
> + return -ENOMEM;
> + }
> + atp->at.attr.mode = S_IRUGO | S_IWUSR;
> + atp->at.attr.name = kstrdup(name, GFP_KERNEL);
> + if (!atp->at.attr.name) {
> + kfree(atp);
> + dev_err(info->dev, "out of kernel memory\n");
> + return -ENOMEM;
> + }
> + atp->at.show = clk_show;
> + atp->at.store = clk_store;
> + err = device_create_file(info->dev, &atp->at);
> + if (err) {
> + kfree(atp->at.attr.name);
> + kfree(atp);
> + dev_err(info->dev, "failed to create file\n");
> + return err;
> + }
> + list_add(&atp->list, &info->head);
> +
> + /* Create parent virtual file */
> + atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
> + if (!atp) {
> + dev_err(info->dev, "out of kernel memory\n");
> + return -ENOMEM;
> + }
> + atp->at.attr.mode = S_IRUGO | S_IWUSR;
> + len = strlen(name) + 8;
> + atp->at.attr.name = kmalloc(len, GFP_KERNEL);
> + if (!atp->at.attr.name) {
> + kfree(atp);
> + dev_err(info->dev, "out of kernel memory\n");
> + return -ENOMEM;
> + }
> + strlcpy((char *)atp->at.attr.name, name, len);
> + strlcat((char *)atp->at.attr.name, "-parent", len);
> + atp->at.show = clk_parent_show;
> + atp->at.store = clk_parent_store;
> + err = device_create_file(info->dev, &atp->at);
> + if (err) {
> + kfree(atp->at.attr.name);
> + kfree(atp);
> + dev_err(info->dev, "failed to create file\n");
> + return err;
> + }
> + list_add(&atp->list, &info->head);
> +
> + info->count++;
> + return 0;
> +}
> +
> +static int probe_driver(struct platform_device *pdev)
> +{
> + struct clk_info *info;
> + int err;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info) {
> + dev_err(&pdev->dev, "out of kernel memory\n");
> + return -ENOMEM;
> + }
> + mutex_init(&info->mutex);
> + INIT_LIST_HEAD(&info->head);
> + info->dev = &pdev->dev;
> + platform_set_drvdata(pdev, info);
> + err = clk_for_each(create_clock_attr, info);
> + if (err)
> + remove_driver(pdev);
> + else
> + dev_notice(&pdev->dev, "probed OK - %d clocks recognized\n",
> + info->count);
> + return err;
> +}
> +
> +static struct platform_driver driver = {
> + .probe = probe_driver,
> + .remove = remove_driver,
> + .driver = {
> + .name = "clocks",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init clk_init_module(void)
> +{
> + return platform_driver_register(&driver);
> +}
> +
> +static void __exit clk_cleanup_module(void)
> +{
> + platform_driver_unregister(&driver);
> +}
> +
> +module_init(clk_init_module);
> +module_exit(clk_cleanup_module);
> +
> +MODULE_AUTHOR("Davide Rizzo <[email protected]>");
> +MODULE_DESCRIPTION("Clock driver");
> +MODULE_SUPPORTED_DEVICE("clocks");
> +MODULE_LICENSE("GPL");
> diff -urNp linux-2.6.28/drivers/uio/Kconfig
> linux-2.6.28.elpa/drivers/uio/Kconfig
> --- linux-2.6.28/drivers/uio/Kconfig 2008-12-25 00:26:37.000000000 +0100
> +++ linux-2.6.28.elpa/drivers/uio/Kconfig 2009-01-07 18:58:10.000000000 +0100
> @@ -13,6 +13,18 @@ menuconfig UIO
>
> if UIO
>
> +
> +config UIO_CLOCK
> + tristate "General purpose clock driver"
> + default y
> + ---help---
> + This driver allows to configure and control internal clocks'
> + rates and parents through virtual files.
> + This driver requires implementation of some additional functions
> + in architecture specific low-level drivers:
> + clk_for_each() and clk_get_name()
> + Currently they're implemented only on Samsung S3C24xx platforms.
> +
> config UIO_CIF
> tristate "generic Hilscher CIF Card driver"
> depends on PCI
> diff -urNp linux-2.6.28/drivers/uio/Makefile
> linux-2.6.28.elpa/drivers/uio/Makefile
> --- linux-2.6.28/drivers/uio/Makefile 2008-12-25 00:26:37.000000000 +0100
> +++ linux-2.6.28.elpa/drivers/uio/Makefile 2009-01-06 18:20:17.000000000 +0100
> @@ -1,4 +1,5 @@
> obj-$(CONFIG_UIO) += uio.o
> +obj-$(CONFIG_UIO_CLOCK) += clock.o
> obj-$(CONFIG_UIO_CIF) += uio_cif.o
> obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o
> obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o

2009-01-08 16:07:17

by Davide Rizzo

[permalink] [raw]
Subject: Re: [PATCH 1/3] Driver for user access to internal clocks

> Davide,
> please don't put this in the drivers/uio/ directory. This is no UIO driver.
>
> Thanks,
> Hans
>
Ok, moved to /drivers/misc

This driver is for user level programs to interact with system clocks.
It allows to read and modify rates and parents, using virtual files.
It requires the implementation of 2 additional functions in the clk interface:
clk_for_each() and clk_name().
Actually I implemented that functions only for Samsung S3C24xx platform.

Signed-off-by: Davide Rizzo <[email protected]>
---
diff -urNp linux-2.6.28/drivers/misc/clock.c
linux-2.6.28.elpa/drivers/misc/clock.c
--- linux-2.6.28/drivers/misc/clock.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.28.elpa/drivers/misc/clock.c 2009-01-08 12:24:04.000000000 +0100
@@ -0,0 +1,313 @@
+/*
+ driver/misc/clock.c
+
+ Written Feb 2008 by Davide Rizzo <[email protected]>
+
+ This driver allows to read and modify internal clocks' rates using
+ virtual files. User can also read and modify parents.
+
+ This driver requires implementation of clk_name() and clk_enum() functions
+ in architecture specific clock.c
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_info {
+ struct mutex mutex;
+ struct list_head head;
+ struct device *dev; /* here because needed by create_clock_attr */
+ int count;
+};
+
+struct attr {
+ struct list_head list;
+ struct device_attribute at;
+};
+
+static ssize_t clk_show(struct device *dev, struct device_attribute *attr,
+ char *buffer)
+{
+ unsigned long rate;
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk = clk_get(dev, attr->attr.name);
+
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ rate = 0;
+
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ rate = clk_get_rate(clk);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+
+ }
+ if (snprintf(buffer, PAGE_SIZE, "%ld\n", rate) > PAGE_SIZE)
+ buffer[PAGE_SIZE - 1] = '\0';
+ return strlen(buffer);
+}
+
+static ssize_t clk_store(struct device *dev, struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ unsigned long rate;
+ struct clk *clk;
+ int err = strict_strtoul(buffer, 10, &rate);
+
+ if (err)
+ return err;
+
+ clk = clk_get(dev, attr->attr.name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ mutex_lock(&info->mutex);
+
+ if (rate != 0) {
+ clk_set_rate(clk, clk_round_rate(clk, rate));
+ clk_enable(clk);
+ } else
+ clk_disable(clk);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+
+ return count;
+}
+
+static ssize_t clk_parent_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk, *parent;
+ char *s = kstrdup(attr->attr.name, GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ s[strlen(attr->attr.name) - 7] = '\0';
+ clk = clk_get(dev, s);
+ kfree(s);
+ buffer[0] = '\0';
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ parent = clk_get_parent(clk);
+ if (parent && !IS_ERR(parent)) {
+ const char *full_name = clk_get_name(parent);
+ if (IS_ERR(full_name)) {
+ mutex_unlock(&info->mutex);
+ return PTR_ERR(full_name);
+ }
+ strlcpy(buffer, full_name, PAGE_SIZE);
+ kfree(full_name);
+ }
+ strlcat(buffer, "\n", PAGE_SIZE);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+ }
+ return strlen(buffer);
+}
+
+static ssize_t clk_parent_store(struct device *dev,
+ struct device_attribute *attr, const char *buffer, size_t count)
+{
+ struct clk_info *info = dev_get_drvdata(dev);
+ struct clk *clk, *parent;
+ char *s;
+
+ parent = clk_get(dev, buffer);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+
+ s = kstrdup(attr->attr.name, GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ s[strlen(attr->attr.name) - 7] = '\0';
+ clk = clk_get(dev, s);
+ kfree(s);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ if (!IS_ERR(clk)) {
+
+ mutex_lock(&info->mutex);
+
+ clk_set_parent(clk, parent);
+
+ mutex_unlock(&info->mutex);
+
+ clk_put(clk);
+ }
+ clk_put(parent);
+ return count;
+}
+
+static int remove_driver(struct platform_device *pdev)
+{
+ struct clk_info *info = platform_get_drvdata(pdev);
+ struct attr *atp;
+
+ mutex_lock(&info->mutex);
+
+ /* Remove all virtual files */
+ while (!list_empty(&info->head)) {
+ atp = list_first_entry(&info->head, struct attr, list);
+ device_remove_file(&pdev->dev, &atp->at);
+ kfree(atp->at.attr.name);
+ list_del(&atp->list);
+ kfree(atp);
+ }
+ platform_set_drvdata(pdev, NULL);
+
+ mutex_unlock(&info->mutex);
+
+ kfree(info);
+ dev_notice(&pdev->dev, "removed\n");
+ return 0;
+}
+
+static int create_clock_attr(struct clk *clk, void *data)
+{
+ struct attr *atp;
+ int err, len;
+ struct clk_info *info = data;
+ const char *name = clk_get_name(clk);
+
+ /* Retrieve clock's name */
+ if (name == NULL || IS_ERR(name)) {
+ /* Don't return error, simply skip this one */
+ dev_err(info->dev, "invalid clock's name\n");
+ return PTR_ERR(name);
+ }
+
+ /* Create rate virtual file */
+ atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
+ if (!atp) {
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.attr.mode = S_IRUGO | S_IWUSR;
+ atp->at.attr.name = kstrdup(name, GFP_KERNEL);
+ if (!atp->at.attr.name) {
+ kfree(atp);
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.show = clk_show;
+ atp->at.store = clk_store;
+ err = device_create_file(info->dev, &atp->at);
+ if (err) {
+ kfree(atp->at.attr.name);
+ kfree(atp);
+ dev_err(info->dev, "failed to create file\n");
+ return err;
+ }
+ list_add(&atp->list, &info->head);
+
+ /* Create parent virtual file */
+ atp = kzalloc(sizeof(struct attr), GFP_KERNEL);
+ if (!atp) {
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ atp->at.attr.mode = S_IRUGO | S_IWUSR;
+ len = strlen(name) + 8;
+ atp->at.attr.name = kmalloc(len, GFP_KERNEL);
+ if (!atp->at.attr.name) {
+ kfree(atp);
+ dev_err(info->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ strlcpy((char *)atp->at.attr.name, name, len);
+ strlcat((char *)atp->at.attr.name, "-parent", len);
+ atp->at.show = clk_parent_show;
+ atp->at.store = clk_parent_store;
+ err = device_create_file(info->dev, &atp->at);
+ if (err) {
+ kfree(atp->at.attr.name);
+ kfree(atp);
+ dev_err(info->dev, "failed to create file\n");
+ return err;
+ }
+ list_add(&atp->list, &info->head);
+
+ info->count++;
+ return 0;
+}
+
+static int probe_driver(struct platform_device *pdev)
+{
+ struct clk_info *info;
+ int err;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "out of kernel memory\n");
+ return -ENOMEM;
+ }
+ mutex_init(&info->mutex);
+ INIT_LIST_HEAD(&info->head);
+ info->dev = &pdev->dev;
+ platform_set_drvdata(pdev, info);
+ err = clk_for_each(create_clock_attr, info);
+ if (err)
+ remove_driver(pdev);
+ else
+ dev_notice(&pdev->dev, "probed OK - %d clocks recognized\n",
+ info->count);
+ return err;
+}
+
+static struct platform_driver driver = {
+ .probe = probe_driver,
+ .remove = remove_driver,
+ .driver = {
+ .name = "clocks",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init clk_init_module(void)
+{
+ return platform_driver_register(&driver);
+}
+
+static void __exit clk_cleanup_module(void)
+{
+ platform_driver_unregister(&driver);
+}
+
+module_init(clk_init_module);
+module_exit(clk_cleanup_module);
+
+MODULE_AUTHOR("Davide Rizzo <[email protected]>");
+MODULE_DESCRIPTION("Clock driver");
+MODULE_SUPPORTED_DEVICE("clocks");
+MODULE_LICENSE("GPL");
diff -urNp linux-2.6.28/drivers/misc/Kconfig
linux-2.6.28.elpa/drivers/misc/Kconfig
--- linux-2.6.28/drivers/misc/Kconfig 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28.elpa/drivers/misc/Kconfig 2009-01-08 16:38:49.000000000 +0100
@@ -500,4 +500,15 @@ config SGI_GRU_DEBUG

source "drivers/misc/c2port/Kconfig"

+config UIO_CLOCK
+ tristate "General purpose clock driver"
+ default y
+ ---help---
+ This driver allows to configure and control internal clocks'
+ rates and parents through virtual files.
+ This driver requires implementation of some additional functions
+ in architecture specific low-level drivers:
+ clk_for_each() and clk_get_name()
+ Currently they're implemented only on Samsung S3C24xx platforms.
+
endif # MISC_DEVICES
diff -urNp linux-2.6.28/drivers/misc/Makefile
linux-2.6.28.elpa/drivers/misc/Makefile
--- linux-2.6.28/drivers/misc/Makefile 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28.elpa/drivers/misc/Makefile 2009-01-08 16:37:48.000000000 +0100
@@ -3,6 +3,7 @@
#
obj- := misc.o # Dummy rule to force built-in.o to be made

+obj-$(CONFIG_UIO_CLOCK) += clock.o
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o

2009-01-09 05:06:14

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 1/3] Driver for user access to internal clocks

On Thu, Jan 08, 2009 at 05:06:58PM +0100, Davide Rizzo wrote:
> > Davide,
> > please don't put this in the drivers/uio/ directory. This is no UIO driver.
> >
> > Thanks,
> > Hans
> >
> Ok, moved to /drivers/misc
>
> This driver is for user level programs to interact with system clocks.

Same comments here as for your "timer.c" file:
- too generic of a name
- needs proper documentation
- send any kernel api changes also to [email protected]

thanks,

greg k-h

2009-01-28 19:17:19

by Davide Rizzo

[permalink] [raw]
Subject: Re: [PATCH 1/3] Driver for user access to internal clocks

> Same comments here as for your "timer.c" file:
> - too generic of a name
> - needs proper documentation
> - send any kernel api changes also to [email protected]
>
> thanks,
>
> greg k-h
>

This is the modified Samsung S3c implementation for user mode clock driver.

This is a proposal for adding 2 functions to clk interface to allow higher
level drivers to enumerate and list all clocks, and to modify clk_get
to accept device specific clk names in the form "name.id"
There is also the implementation for Samsung S3C24xx platform.

Signed-off-by: Davide Rizzo <[email protected]>
---diff -urpN linux-2.6.29-rc2/arch/arm/plat-s3c/clock.c
linux-2.6.29-rc2.elpa/arch/arm/plat-s3c/clock.c
--- linux-2.6.29-rc2/arch/arm/plat-s3c/clock.c 2009-01-17
11:32:38.000000000 +0100
+++ linux-2.6.29-rc2.elpa/arch/arm/plat-s3c/clock.c 2009-01-28
19:32:08.000000000 +0100
@@ -69,11 +69,19 @@ static int clk_null_enable(struct clk *c

struct clk *clk_get(struct device *dev, const char *id)
{
+ long idno;
+ char *name = (char *)id;
+ char *dotpos = strrchr(id, '.');
struct clk *p;
struct clk *clk = ERR_PTR(-ENOENT);
- int idno;

- if (dev == NULL || dev->bus != &platform_bus_type)
+ if (dotpos) {
+ int err = strict_strtol(dotpos + 1, 10, &idno);
+ if (err)
+ return ERR_PTR(err);
+ name = kstrdup(id, GFP_KERNEL);
+ name[dotpos - id] = '\0';
+ } else if (dev == NULL || dev->bus != &platform_bus_type)
idno = -1;
else
idno = to_platform_device(dev)->id;
@@ -82,7 +90,7 @@ struct clk *clk_get(struct device *dev,

list_for_each_entry(p, &clocks, list) {
if (p->id == idno &&
- strcmp(id, p->name) == 0 &&
+ strcmp(name, p->name) == 0 &&
try_module_get(p->owner)) {
clk = p;
break;
@@ -94,7 +102,7 @@ struct clk *clk_get(struct device *dev,

if (IS_ERR(clk)) {
list_for_each_entry(p, &clocks, list) {
- if (p->id == -1 && strcmp(id, p->name) == 0 &&
+ if (p->id == -1 && strcmp(name, p->name) == 0 &&
try_module_get(p->owner)) {
clk = p;
break;
@@ -103,6 +111,8 @@ struct clk *clk_get(struct device *dev,
}

spin_unlock(&clocks_lock);
+ if (dotpos)
+ kfree(name);
return clk;
}

@@ -222,6 +232,42 @@ EXPORT_SYMBOL(clk_set_rate);
EXPORT_SYMBOL(clk_get_parent);
EXPORT_SYMBOL(clk_set_parent);

+int clk_for_each(int(*fn)(struct clk *, void *), void *data)
+ {
+ struct clk *clk;
+ int ret = 0;
+
+ list_for_each_entry(clk, &clocks, list)
+ {
+ ret = fn(clk, data);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(clk_for_each);
+
+/* Returned pointer must be deallocated with kfree() */
+const char *clk_get_name(struct clk *clk)
+{
+ char *s;
+ int len;
+
+ if (IS_ERR(clk))
+ return ERR_PTR(-EINVAL);
+
+ len = strlen(clk->name) + 8;
+ s = kmalloc(len, GFP_KERNEL);
+ if (!s)
+ return ERR_PTR(-ENOMEM);
+ if (clk->id == -1)
+ strlcpy(s, clk->name, len);
+ else if (snprintf(s, len, "%s.%d", clk->name, clk->id) > len)
+ s[len - 1] = '\0';
+ return s;
+}
+EXPORT_SYMBOL(clk_get_name);
+
/* base clocks */

static int clk_default_setrate(struct clk *clk, unsigned long rate)
diff -urpN linux-2.6.29-rc2/include/linux/clk.h
linux-2.6.29-rc2.elpa/include/linux/clk.h
--- linux-2.6.29-rc2/include/linux/clk.h 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.29-rc2.elpa/include/linux/clk.h 2009-01-28
19:21:43.000000000 +0100
@@ -125,4 +125,22 @@ int clk_set_parent(struct clk *clk, stru
*/
struct clk *clk_get_parent(struct clk *clk);

+/**
+ * clk_for_each - calls fn, iterating between registered clocks
+ * @fn: function to be called, passing each clk structure in 1st argument
+ * @data: 2nd argument to pass to fn function
+ *
+ * Returns 0 or the called fn return code if != 0
+ */
+int clk_for_each(int(*fn)(struct clk *, void *), void *data);
+
+/**
+ * clk_name - get clock name
+ * @clk: clock source
+ *
+ * Returns clock's name that must be deallocated with kfree(), or valid
+ * IS_ERR() condition containing errno.
+ */
+const char *clk_get_name(struct clk *clk);
+
#endif

2009-01-28 19:20:26

by Davide Rizzo

[permalink] [raw]
Subject: Re: [PATCH 1/3] Driver for user access to internal clocks

> Same comments here as for your "timer.c" file:
> - too generic of a name
> - needs proper documentation
> - send any kernel api changes also to [email protected]
>
> thanks,
>
> greg k-h
>
Here is the modified user mode clock access driver.
I moved it in /drivers/misc and reorganized virtual files under /sys/class/clock

This driver is for user level programs to interact with system clocks.
It allows to read and modify rates and parents, using virtual files.
It requires the implementation of 2 additional functions in the clk interface:
clk_for_each() and clk_name().
Actually I implemented that functions only for Samsung S3C24xx platform.

Signed-off-by: Davide Rizzo <[email protected]>
---
diff -urpN linux-2.6.29-rc2/drivers/misc/Kconfig
linux-2.6.29-rc2.elpa/drivers/misc/Kconfig
--- linux-2.6.29-rc2/drivers/misc/Kconfig 2009-01-17 11:32:40.000000000 +0100
+++ linux-2.6.29-rc2.elpa/drivers/misc/Kconfig 2009-01-28
19:21:43.000000000 +0100
@@ -232,4 +232,19 @@ config DELL_LAPTOP

source "drivers/misc/c2port/Kconfig"

+config UM_CLOCK
+ bool "User mode clock driver"
+ depends on SYSFS
+ default n
+ ---help---
+ Allows user mode programs to configure and control internal clocks'
+ rates and parents through virtual files in sysfs.
+ This driver requires implementation of some additional functions
+ in architecture specific low-level drivers: clk_for_each() and
+ clk_get_name(). Moreover, it requires clk_get() to recognize the
+ "name.id" format
+ Currently they're implemented only on Samsung S3C24xx platforms.
+
+ If unsure, say N.
+
endif # MISC_DEVICES
diff -urpN linux-2.6.29-rc2/drivers/misc/Makefile
linux-2.6.29-rc2.elpa/drivers/misc/Makefile
--- linux-2.6.29-rc2/drivers/misc/Makefile 2009-01-17 11:32:40.000000000 +0100
+++ linux-2.6.29-rc2.elpa/drivers/misc/Makefile 2009-01-28
19:21:43.000000000 +0100
@@ -20,3 +20,4 @@ obj-$(CONFIG_SGI_XP) += sgi-xp/
obj-$(CONFIG_SGI_GRU) += sgi-gru/
obj-$(CONFIG_HP_ILO) += hpilo.o
obj-$(CONFIG_C2PORT) += c2port/
+obj-$(CONFIG_UM_CLOCK) += um_clock.o
diff -urpN linux-2.6.29-rc2/drivers/misc/um_clock.c
linux-2.6.29-rc2.elpa/drivers/misc/um_clock.c
--- linux-2.6.29-rc2/drivers/misc/um_clock.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.29-rc2.elpa/drivers/misc/um_clock.c 2009-01-28
19:21:43.000000000 +0100
@@ -0,0 +1,174 @@
+/*
+ driver/misc/clock.c
+
+ Written Feb 2008 by Davide Rizzo <[email protected]>
+
+ This driver allows to read and modify internal clocks' rates using
+ virtual files. User can also read and modify parents.
+
+ This driver requires implementation of clk_name() and clk_enum() functions
+ in architecture specific clock.c
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/kdev_t.h>
+#include <linux/err.h>
+
+static DEFINE_MUTEX(mutex);
+
+static ssize_t rate_show(struct device *dev, struct device_attribute *attr,
+ char *buffer)
+{
+ unsigned long rate;
+ struct clk *clk;
+
+ clk = dev_get_drvdata(dev);
+ rate = 0;
+
+ mutex_lock(&mutex);
+
+ rate = clk_get_rate(clk);
+
+ mutex_unlock(&mutex);
+
+ return sprintf(buffer, "%ld\n", rate);
+}
+
+static ssize_t rate_store(struct device *dev, struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long rate;
+ struct clk *clk;
+ int err;
+
+ clk = dev_get_drvdata(dev);
+ err = strict_strtoul(buffer, 10, &rate);
+ if (err)
+ return err;
+
+ mutex_lock(&mutex);
+
+ if (rate != 0) {
+ clk_set_rate(clk, clk_round_rate(clk, rate));
+ clk_enable(clk);
+ } else
+ clk_disable(clk);
+
+ mutex_unlock(&mutex);
+
+ return count;
+}
+
+static ssize_t parent_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct clk *parent, *clk;
+
+ clk = dev_get_drvdata(dev);
+
+ mutex_lock(&mutex);
+
+ parent = clk_get_parent(clk);
+ if (parent && !IS_ERR(parent)) {
+ const char *name = clk_get_name(parent);
+ if (IS_ERR(name)) {
+ mutex_unlock(&mutex);
+ return PTR_ERR(name);
+ }
+ strlcpy(buffer, name, PAGE_SIZE);
+ } else
+ buffer[0] = '\0';
+ strlcat(buffer, "\n", PAGE_SIZE);
+
+ mutex_unlock(&mutex);
+
+ return strlen(buffer);
+}
+
+static ssize_t parent_store(struct device *dev,
+ struct device_attribute *attr, const char *buffer, size_t count)
+{
+ struct clk *parent;
+
+ char *s = kstrdup(buffer, GFP_KERNEL);
+
+ if (!s)
+ return -ENOMEM;
+ if (s[strlen(s) - 1] == '\n')
+ s[strlen(s) - 1] = '\0';
+ parent = clk_get(dev, s);
+ kfree(s);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+
+ mutex_lock(&mutex);
+
+ clk_set_parent(dev_get_drvdata(dev), parent);
+
+ mutex_unlock(&mutex);
+
+ clk_put(parent);
+ return count;
+}
+
+static DEVICE_ATTR(rate, S_IRUGO | S_IWUSR, rate_show, rate_store);
+static DEVICE_ATTR(parent, S_IRUGO | S_IWUSR, parent_show, parent_store);
+
+static const struct attribute *clock_attrs[] = {
+ &dev_attr_rate.attr,
+ &dev_attr_parent.attr,
+ NULL,
+};
+
+static const struct attribute_group clock_attr_group = {
+ .attrs = (struct attribute **) clock_attrs,
+};
+
+static struct class gpclock_class = {
+ .name = "clock",
+ .owner = THIS_MODULE,
+};
+
+static int create_clock_attr(struct clk *clk, void *data)
+{
+ struct device *dev;
+ const char *name;
+
+ name = clk_get_name(clk);
+ if (name == NULL || IS_ERR(name)) {
+ pr_debug("Invalid clock's name\n");
+ return PTR_ERR(name);
+ }
+ dev = device_create(&gpclock_class, (struct device *)data, MKDEV(0, 0),
+ clk, name);
+ if (!dev)
+ return -ENODEV;
+ return sysfs_create_group(&dev->kobj, &clock_attr_group);
+}
+
+static int __init gpclock_init(void)
+{
+ int ret;
+
+ ret = class_register(&gpclock_class);
+ if (ret >= 0)
+ ret = clk_for_each(create_clock_attr, NULL);
+ return ret;
+}
+subsys_initcall(gpclock_init);
+
diff -urpN linux-2.6.29-rc2/Documentation/ABI/testing/sysfs-class-clock
linux-2.6.29-rc2.elpa/Documentation/ABI/testing/sysfs-class-clock
--- linux-2.6.29-rc2/Documentation/ABI/testing/sysfs-class-clock 1970-01-01
01:00:00.000000000 +0100
+++ linux-2.6.29-rc2.elpa/Documentation/ABI/testing/sysfs-class-clock 2009-01-28
19:21:43.000000000 +0100
@@ -0,0 +1,20 @@
+What: /sys/class/clock
+Date: January 2009
+Contact: Davide Rizzo <[email protected]>
+Description:
+ The /sys/class/clock directory will consist of a group of
+ subdirectories each one describing an internal clock in the kernel.
+ The name of the subdirectory is the clock's name.
+ If a clock is device specific, its name is in the form "name.id".
+ In each directory there are 2 virtual files: rate and parent.
+ Both these files are r/w, but there can be some architecture-dependent
+ limitations on write access on one or both of them.
+ For this interface to work, it needs some low-level implementations (look
+ at include/linux/clk.h):
+ - clk_for_each()
+ - clk_name()
+ - clk_get() should recognize also the "name.id" format
+ Actually I implemented these only for Samsung S3C SoC in
+ arch/arm/plat-s3c/clock.c
+
+Users: