summaryrefslogtreecommitdiff
path: root/drivers/clk/clk-ipaq.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/clk-ipaq.c')
-rw-r--r--drivers/clk/clk-ipaq.c135
1 files changed, 135 insertions, 0 deletions
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");