#include #include #include #include #include #include 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 "); MODULE_DESCRIPTION("iPAQ Audio clock driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:ipaq-audio-clock");