Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.5.6.
  1/*
  2 * Copyright (C) 2014 Philipp Zabel, Pengutronix
  3 *
  4 * This program is free software; you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License version 2 as
  6 * published by the Free Software Foundation.
  7 *
  8 * PWM (mis)used as clock output
  9 */
 10#include <linux/clk-provider.h>
 11#include <linux/kernel.h>
 12#include <linux/module.h>
 13#include <linux/of.h>
 14#include <linux/platform_device.h>
 15#include <linux/pwm.h>
 16
 17struct clk_pwm {
 18	struct clk_hw hw;
 19	struct pwm_device *pwm;
 20	u32 fixed_rate;
 21};
 22
 23static inline struct clk_pwm *to_clk_pwm(struct clk_hw *hw)
 24{
 25	return container_of(hw, struct clk_pwm, hw);
 26}
 27
 28static int clk_pwm_prepare(struct clk_hw *hw)
 29{
 30	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
 31
 32	return pwm_enable(clk_pwm->pwm);
 33}
 34
 35static void clk_pwm_unprepare(struct clk_hw *hw)
 36{
 37	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
 38
 39	pwm_disable(clk_pwm->pwm);
 40}
 41
 42static unsigned long clk_pwm_recalc_rate(struct clk_hw *hw,
 43					 unsigned long parent_rate)
 44{
 45	struct clk_pwm *clk_pwm = to_clk_pwm(hw);
 46
 47	return clk_pwm->fixed_rate;
 48}
 49
 50static const struct clk_ops clk_pwm_ops = {
 51	.prepare = clk_pwm_prepare,
 52	.unprepare = clk_pwm_unprepare,
 53	.recalc_rate = clk_pwm_recalc_rate,
 54};
 55
 56static int clk_pwm_probe(struct platform_device *pdev)
 57{
 58	struct device_node *node = pdev->dev.of_node;
 59	struct clk_init_data init;
 60	struct clk_pwm *clk_pwm;
 61	struct pwm_device *pwm;
 62	struct pwm_args pargs;
 63	const char *clk_name;
 64	int ret;
 65
 66	clk_pwm = devm_kzalloc(&pdev->dev, sizeof(*clk_pwm), GFP_KERNEL);
 67	if (!clk_pwm)
 68		return -ENOMEM;
 69
 70	pwm = devm_pwm_get(&pdev->dev, NULL);
 71	if (IS_ERR(pwm))
 72		return PTR_ERR(pwm);
 73
 74	pwm_get_args(pwm, &pargs);
 75	if (!pargs.period) {
 76		dev_err(&pdev->dev, "invalid PWM period\n");
 77		return -EINVAL;
 78	}
 79
 80	if (of_property_read_u32(node, "clock-frequency", &clk_pwm->fixed_rate))
 81		clk_pwm->fixed_rate = NSEC_PER_SEC / pargs.period;
 82
 83	if (pargs.period != NSEC_PER_SEC / clk_pwm->fixed_rate &&
 84	    pargs.period != DIV_ROUND_UP(NSEC_PER_SEC, clk_pwm->fixed_rate)) {
 85		dev_err(&pdev->dev,
 86			"clock-frequency does not match PWM period\n");
 87		return -EINVAL;
 88	}
 89
 90	/*
 91	 * FIXME: pwm_apply_args() should be removed when switching to the
 92	 * atomic PWM API.
 93	 */
 94	pwm_apply_args(pwm);
 95	ret = pwm_config(pwm, (pargs.period + 1) >> 1, pargs.period);
 96	if (ret < 0)
 97		return ret;
 98
 99	clk_name = node->name;
100	of_property_read_string(node, "clock-output-names", &clk_name);
101
102	init.name = clk_name;
103	init.ops = &clk_pwm_ops;
104	init.flags = CLK_IS_BASIC;
105	init.num_parents = 0;
106
107	clk_pwm->pwm = pwm;
108	clk_pwm->hw.init = &init;
109	ret = devm_clk_hw_register(&pdev->dev, &clk_pwm->hw);
110	if (ret)
111		return ret;
112
113	return of_clk_add_hw_provider(node, of_clk_hw_simple_get, &clk_pwm->hw);
114}
115
116static int clk_pwm_remove(struct platform_device *pdev)
117{
118	of_clk_del_provider(pdev->dev.of_node);
119
120	return 0;
121}
122
123static const struct of_device_id clk_pwm_dt_ids[] = {
124	{ .compatible = "pwm-clock" },
125	{ }
126};
127MODULE_DEVICE_TABLE(of, clk_pwm_dt_ids);
128
129static struct platform_driver clk_pwm_driver = {
130	.probe = clk_pwm_probe,
131	.remove = clk_pwm_remove,
132	.driver = {
133		.name = "pwm-clock",
134		.of_match_table = of_match_ptr(clk_pwm_dt_ids),
135	},
136};
137
138module_platform_driver(clk_pwm_driver);
139
140MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>");
141MODULE_DESCRIPTION("PWM clock driver");
142MODULE_LICENSE("GPL");