pwm-puv3.c 3.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
/*
 * linux/arch/unicore32/kernel/pwm.c
 *
 * Code specific to PKUnity SoC and UniCore ISA
 *
 *	Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
 *	Copyright (C) 2001-2010 Guan Xuetao
 *
 * 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.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>

#include <asm/div64.h>
#include <mach/hardware.h>

struct puv3_pwm_chip {
	struct pwm_chip chip;
	void __iomem *base;
	struct clk *clk;
	bool enabled;
};

static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip)
{
	return container_of(chip, struct puv3_pwm_chip, chip);
}

/*
 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
 * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
 */
static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			   int duty_ns, int period_ns)
{
	unsigned long period_cycles, prescale, pv, dc;
	struct puv3_pwm_chip *puv3 = to_puv3(chip);
	unsigned long long c;

	c = clk_get_rate(puv3->clk);
	c = c * period_ns;
	do_div(c, 1000000000);
	period_cycles = c;

	if (period_cycles < 1)
		period_cycles = 1;

	prescale = (period_cycles - 1) / 1024;
	pv = period_cycles / (prescale + 1) - 1;

	if (prescale > 63)
		return -EINVAL;

	if (duty_ns == period_ns)
		dc = OST_PWMDCCR_FDCYCLE;
	else
		dc = (pv + 1) * duty_ns / period_ns;

	/*
	 * NOTE: the clock to PWM has to be enabled first
	 * before writing to the registers
	 */
	clk_prepare_enable(puv3->clk);

	writel(prescale, puv3->base + OST_PWM_PWCR);
	writel(pv - dc, puv3->base + OST_PWM_DCCR);
	writel(pv, puv3->base + OST_PWM_PCR);

	clk_disable_unprepare(puv3->clk);

	return 0;
}

static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct puv3_pwm_chip *puv3 = to_puv3(chip);

	return clk_prepare_enable(puv3->clk);
}

static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct puv3_pwm_chip *puv3 = to_puv3(chip);

	clk_disable_unprepare(puv3->clk);
}

static const struct pwm_ops puv3_pwm_ops = {
	.config = puv3_pwm_config,
	.enable = puv3_pwm_enable,
	.disable = puv3_pwm_disable,
	.owner = THIS_MODULE,
};

static int __devinit pwm_probe(struct platform_device *pdev)
{
	struct puv3_pwm_chip *puv3;
	struct resource *r;
	int ret;

	puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL);
	if (puv3 == NULL) {
		dev_err(&pdev->dev, "failed to allocate memory\n");
		return -ENOMEM;
	}

	puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK");
	if (IS_ERR(puv3->clk))
		return PTR_ERR(puv3->clk);

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (r == NULL) {
		dev_err(&pdev->dev, "no memory resource defined\n");
		return -ENODEV;
	}

	puv3->base = devm_request_and_ioremap(&pdev->dev, r);
	if (puv3->base == NULL)
		return -EADDRNOTAVAIL;

	puv3->chip.dev = &pdev->dev;
	puv3->chip.ops = &puv3_pwm_ops;
	puv3->chip.base = -1;
	puv3->chip.npwm = 1;

	ret = pwmchip_add(&puv3->chip);
	if (ret < 0) {
		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
		return ret;
	}

	platform_set_drvdata(pdev, puv3);
	return 0;
}

static int __devexit pwm_remove(struct platform_device *pdev)
{
	struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev);

	return pwmchip_remove(&puv3->chip);
}

static struct platform_driver puv3_pwm_driver = {
	.driver = {
		.name = "PKUnity-v3-PWM",
	},
	.probe = pwm_probe,
	.remove = __devexit_p(pwm_remove),
};
module_platform_driver(puv3_pwm_driver);

MODULE_LICENSE("GPL v2");