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
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
> 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
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
> 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
> 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: