[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <8447d6730901080149l4146139an9fad00fd88d72fb9@mail.gmail.com>
Date: Thu, 8 Jan 2009 10:49:12 +0100
From: "Davide Rizzo" <elpa.rizzo@...il.com>
To: linux-kernel@...r.kernel.org, hjk@...utronix.de, gregkh@...e.de,
ben-linux@...ff.org
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 <elpa-rizzo@...il.com>
---
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 <davide@...a.it>
+
+ 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 <davide@...a.it>");
+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
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists