diff --git a/Documentation/devicetree/bindings/clock/pwm-clock.txt b/Documentation/devicetree/bindings/clock/pwm-clock.txt new file mode 100644 index 000000000000..83db876b3b90 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/pwm-clock.txt @@ -0,0 +1,26 @@ +Binding for an external clock signal driven by a PWM pin. + +This binding uses the common clock binding[1] and the common PWM binding[2]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt +[2] Documentation/devicetree/bindings/pwm/pwm.txt + +Required properties: +- compatible : shall be "pwm-clock". +- #clock-cells : from common clock binding; shall be set to 0. +- pwms : from common PWM binding; this determines the clock frequency + via the period given in the PWM specifier. + +Optional properties: +- clock-output-names : From common clock binding. +- clock-frequency : Exact output frequency, in case the PWM period + is not exact but was rounded to nanoseconds. + +Example: + clock { + compatible = "pwm-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "mipi_mclk"; + pwms = <&pwm2 0 40>; /* 1 / 40 ns = 25 MHz */ + }; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0b474a04730f..9897f353bf1a 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -130,6 +130,13 @@ config COMMON_CLK_PALMAS This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO using common clock framework. +config COMMON_CLK_PWM + tristate "Clock driver for PWMs used as clock outputs" + depends on PWM + ---help--- + Adapter driver so that any PWM output can be (mis)used as clock signal + at 50% duty cycle. + config COMMON_CLK_PXA def_bool COMMON_CLK && ARCH_PXA ---help--- diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 1d35f3bb28e0..492bcabe7930 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_ARCH_U300) += clk-u300.o obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o +obj-$(CONFIG_COMMON_CLK_PWM) += clk-pwm.o obj-$(CONFIG_COMMON_CLK_AT91) += at91/ obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ diff --git a/drivers/clk/clk-pwm.c b/drivers/clk/clk-pwm.c new file mode 100644 index 000000000000..328fcfcefd8c --- /dev/null +++ b/drivers/clk/clk-pwm.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2014 Philipp Zabel, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * PWM (mis)used as clock output + */ +#include +#include +#include +#include +#include +#include + +struct clk_pwm { + struct clk_hw hw; + struct pwm_device *pwm; + u32 fixed_rate; +}; + +static inline struct clk_pwm *to_clk_pwm(struct clk_hw *hw) +{ + return container_of(hw, struct clk_pwm, hw); +} + +static int clk_pwm_prepare(struct clk_hw *hw) +{ + struct clk_pwm *clk_pwm = to_clk_pwm(hw); + + return pwm_enable(clk_pwm->pwm); +} + +static void clk_pwm_unprepare(struct clk_hw *hw) +{ + struct clk_pwm *clk_pwm = to_clk_pwm(hw); + + pwm_disable(clk_pwm->pwm); +} + +static unsigned long clk_pwm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_pwm *clk_pwm = to_clk_pwm(hw); + + return clk_pwm->fixed_rate; +} + +static const struct clk_ops clk_pwm_ops = { + .prepare = clk_pwm_prepare, + .unprepare = clk_pwm_unprepare, + .recalc_rate = clk_pwm_recalc_rate, +}; + +static int clk_pwm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct clk_init_data init; + struct clk_pwm *clk_pwm; + struct pwm_device *pwm; + const char *clk_name; + struct clk *clk; + int ret; + + clk_pwm = devm_kzalloc(&pdev->dev, sizeof(*clk_pwm), GFP_KERNEL); + if (!clk_pwm) + return -ENOMEM; + + pwm = devm_pwm_get(&pdev->dev, NULL); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + if (!pwm->period) { + dev_err(&pdev->dev, "invalid PWM period\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "clock-frequency", &clk_pwm->fixed_rate)) + clk_pwm->fixed_rate = NSEC_PER_SEC / pwm->period; + + if (pwm->period != NSEC_PER_SEC / clk_pwm->fixed_rate && + pwm->period != DIV_ROUND_UP(NSEC_PER_SEC, clk_pwm->fixed_rate)) { + dev_err(&pdev->dev, + "clock-frequency does not match PWM period\n"); + return -EINVAL; + } + + ret = pwm_config(pwm, (pwm->period + 1) >> 1, pwm->period); + if (ret < 0) + return ret; + + clk_name = node->name; + of_property_read_string(node, "clock-output-names", &clk_name); + + init.name = clk_name; + init.ops = &clk_pwm_ops; + init.flags = CLK_IS_BASIC | CLK_IS_ROOT; + init.num_parents = 0; + + clk_pwm->pwm = pwm; + clk_pwm->hw.init = &init; + clk = devm_clk_register(&pdev->dev, &clk_pwm->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + return of_clk_add_provider(node, of_clk_src_simple_get, clk); +} + +static int clk_pwm_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + +static const struct of_device_id clk_pwm_dt_ids[] = { + { .compatible = "pwm-clock" }, + { } +}; +MODULE_DEVICE_TABLE(of, clk_pwm_dt_ids); + +static struct platform_driver clk_pwm_driver = { + .probe = clk_pwm_probe, + .remove = clk_pwm_remove, + .driver = { + .name = "pwm-clock", + .of_match_table = of_match_ptr(clk_pwm_dt_ids), + }, +}; + +module_platform_driver(clk_pwm_driver); + +MODULE_AUTHOR("Philipp Zabel "); +MODULE_DESCRIPTION("PWM clock driver"); +MODULE_LICENSE("GPL");