diff options
author | Russell King <rmk+kernel@armlinux.org.uk> | 2016-09-26 18:53:39 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@armlinux.org.uk> | 2020-10-12 21:56:19 +0100 |
commit | f81d38f4dba0b8e2228d10c9b3db59632344690c (patch) | |
tree | d074361150e69537ca16128e73aa47853817f36c | |
parent | e7d29477757885346d9a537c6f496fd10cd5f24c (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.c | 18 | ||||
-rw-r--r-- | drivers/clk/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/clk-ipaq.c | 135 |
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"); |