summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2016-09-26 18:53:39 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2020-10-12 21:56:19 +0100
commitf81d38f4dba0b8e2228d10c9b3db59632344690c (patch)
treed074361150e69537ca16128e73aa47853817f36c
parente7d29477757885346d9a537c6f496fd10cd5f24c (diff)
clk: add iPAQ audio sysclock driver
Add clock driver for iPAQ audio sysclock. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--arch/arm/mach-sa1100/h3600.c18
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-ipaq.c135
3 files changed, 154 insertions, 0 deletions
diff --git a/arch/arm/mach-sa1100/h3600.c b/arch/arm/mach-sa1100/h3600.c
index 28ce2d1353c7..029f949cbc56 100644
--- a/arch/arm/mach-sa1100/h3600.c
+++ b/arch/arm/mach-sa1100/h3600.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/gpio/machine.h>
+#include <linux/platform_device.h>
#include <video/sa1100fb.h>
@@ -116,10 +117,27 @@ static struct gpiod_lookup_table h3600_sleeve_gpio_table = {
},
};
+static struct gpiod_lookup_table h3600_audio_clk_gpio_table = {
+ .dev_id = "ipaq-audio-clk",
+ .table = {
+ GPIO_LOOKUP("gpio", 12, "clk0", GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP("gpio", 13, "clk1", GPIO_ACTIVE_HIGH),
+ { },
+ },
+};
+
+static struct platform_device_info h3600_audio_clk_device = {
+ .name = "ipaq-audio-clk",
+ .id = -1,
+};
+
static void __init h3600_mach_init(void)
{
gpiod_add_lookup_table(&h3600_irda_gpio_table);
gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
+ gpiod_add_lookup_table(&h3600_audio_clk_gpio_table);
+
+ platform_device_register_full(&h3600_audio_clk_device);
h3xxx_mach_init();
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index da8fcf147eb1..d80fa7dbc3ad 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_COMMON_CLK_ASPEED) += clk-aspeed.o
obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o
+obj-$(CONFIG_SA1100_H3600) += clk-ipaq.o
obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o
obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o
diff --git a/drivers/clk/clk-ipaq.c b/drivers/clk/clk-ipaq.c
new file mode 100644
index 000000000000..af48cdb7da53
--- /dev/null
+++ b/drivers/clk/clk-ipaq.c
@@ -0,0 +1,135 @@
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct ipaq_audio_clk {
+ struct clk_hw clk;
+ struct clk_lookup *cl;
+ struct gpio_desc *gpios[2];
+};
+#define to_ipaq_audio_clk(x) container_of(x, struct ipaq_audio_clk, clk)
+
+static const char *clk_names[2] = { "clk0", "clk1" };
+
+static unsigned long ipaq_audio_clk_rates[] = {
+ 12288000,
+ 11289600,
+ 4096000,
+ 5624500,
+};
+
+static unsigned long ipaq_audio_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+ unsigned int clk0 = !!gpiod_get_value(ia->gpios[0]);
+ unsigned int clk1 = !!gpiod_get_value(ia->gpios[1]);
+ unsigned int idx = clk1 * 2 + clk0;
+
+ return ipaq_audio_clk_rates[idx];
+}
+
+static long ipaq_audio_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int i, best = -1;
+ long diff, best_diff = LONG_MAX;
+
+ for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+ diff = ipaq_audio_clk_rates[i] - rate;
+ if (diff < 0)
+ diff = -diff;
+ if (diff < best_diff) {
+ best_diff = diff;
+ best = i;
+ }
+ }
+
+ return best == -1 ? -EINVAL : ipaq_audio_clk_rates[best];
+}
+
+static int ipaq_audio_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+ DECLARE_BITMAP(values, 2) = { 0, };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+ if (ipaq_audio_clk_rates[i] == rate) {
+ __assign_bit(0, values, !!(i & 1));
+ __assign_bit(1, values, !!(i & 2));
+
+ gpiod_set_array_value(2, ia->gpios, NULL, values);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static const struct clk_ops ipaq_audio_clk_ops = {
+ .recalc_rate = ipaq_audio_clk_recalc_rate,
+ .round_rate = ipaq_audio_clk_round_rate,
+ .set_rate = ipaq_audio_clk_set_rate,
+};
+
+static struct clk_init_data ipaq_audio_clk_init = {
+ .name = "ipaq-audio",
+ .ops = &ipaq_audio_clk_ops,
+};
+
+static int ipaq_audio_clk_probe(struct platform_device *pdev)
+{
+ struct ipaq_audio_clk *ia;
+ int i, ret;
+
+ ia = devm_kzalloc(&pdev->dev, sizeof(*ia), GFP_KERNEL);
+ if (!ia)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(clk_names); i++) {
+ ia->gpios[i] = devm_gpiod_get(&pdev->dev, clk_names[i],
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ia->gpios[i]))
+ return PTR_ERR(ia->gpios[i]);
+ }
+
+ platform_set_drvdata(pdev, ia);
+
+ ia->clk.init = &ipaq_audio_clk_init;
+
+ ret = devm_clk_hw_register(&pdev->dev, &ia->clk);
+ if (ret)
+ return ret;
+
+ ia->cl = clkdev_hw_create(&ia->clk, "ext", "h3xxx-asoc");
+
+ return ia->cl ? 0 : -ENOMEM;
+}
+
+static int ipaq_audio_clk_remove(struct platform_device *pdev)
+{
+ struct ipaq_audio_clk *ia = platform_get_drvdata(pdev);
+
+ clkdev_drop(ia->cl);
+
+ return 0;
+}
+
+static struct platform_driver ipaq_audio_clk_driver = {
+ .probe = ipaq_audio_clk_probe,
+ .remove = ipaq_audio_clk_remove,
+ .driver = {
+ .name = "ipaq-audio-clk",
+ },
+};
+
+module_platform_driver(ipaq_audio_clk_driver);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
+MODULE_DESCRIPTION("iPAQ Audio clock driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ipaq-audio-clock");