内核版本:4.14.0
基于设备树
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/io.h>
#include <linux/clk.h>
#define DEVICE_NAME "xxx" /* Device name */
#define COMPAT_PROPT "xxx,xxx" /* Compatible property of the device matched with this driver */
#define CLK_NAME "xxx"
#define S_2_NS 1000000000
/* PWM register define */
#define PWM_AXI_CTRL_REG_OFFSET 0x00
#define PWM_AXI_STATUS_REG_OFFSET 0x04
#define PWM_AXI_PERIOD_REG_OFFSET 0x08
#define PWM_AXI_DUTY_REG_OFFSET 0x40
/* Device information structure. */
struct pwm_info {
struct device *p_dev;
struct pwm_chip chip; /* describe a PWM device */
struct clk *p_pwmclk; /* PWM input clock */
void __iomem *baseaddr; /* PWM register base addr */
unsigned int period_min_ns /* PWM min period */
};
/*
* @description: PWM period and duty cycle configuration function.
* @param – chip: The pointer to struct pwm_chip.
* @param - pwm: The pointer to struct pwm_device, means pwm channel(s).
* @param - duty_ns: PWM duty cycle in ns.
* @param - period_ns: PWM period in ns.
* @return: 0: Successful; Negative: Failed.
*/
static int axi_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns)
{
u32 duty_mult, period_mult;
struct pwm_info *p_pwminfo = container_of(chip, struct pwm_info, chip);
if (period_ns < p_pwminfo->period_min_ns)
period_ns = p_pwminfo->period_min_ns;
period_mult = period_ns / p_pwminfo->period_min_ns;
duty_mult = duty_ns / p_pwminfo->period_min_ns;
printk(KERN_INFO "period:%d*%dns, duty:%d*%dns\n", period_mult,
p_pwminfo->period_min_ns, duty_mult, p_pwminfo->period_min_ns);
writel(period_mult, p_pwminfo->baseaddr+PWM_AXI_PERIOD_REG_OFFSET);
writel(duty_mult, p_pwminfo->baseaddr+PWM_AXI_DUTY_REG_OFFSET+4*pwm->hwpwm);
return 0;
}
/*
* @description: PWM enable function.
* @param – chip: The pointer to struct pwm_chip.
* @param - pwm: The pointer to struct pwm_device, means pwm channel(s).
* @return: 0: Successful; Negative: Failed.
*/
static int axi_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_info *p_pwminfo = container_of(chip, struct pwm_info, chip);
writel(1, p_pwminfo->baseaddr+PWM_AXI_CTRL_REG_OFFSET);
return 0;
}
/*
* @description: PWM disable function.
* @param – chip: The pointer to struct pwm_chip.
* @param - pwm: The pointer to struct pwm_device, means pwm channel(s).
* @return: None.
*/
static void axi_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_info *p_pwminfo = container_of(chip, struct pwm_info, chip);
writel(0, p_pwminfo->baseaddr+PWM_AXI_CTRL_REG_OFFSET);
}
/* The pwm operation function structure. */
static const struct pwm_ops pwm_fops = {
.owner = THIS_MODULE,
.config = axi_pwm_config, /* pwm config function */
.enable = axi_pwm_enable, /* pwm enable function */
.disable = axi_pwm_disable, /* pwm disable function */
};
/*
* @description : Initialize the pwm.
* @param -pdev: Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int pwm_init(struct platform_device *pdev)
{
int ret;
const char *str;
unsigned long clk_rate;
struct resource *res;
struct pwm_info *p_pwminfo = platform_get_drvdata(pdev);
/* Get the platform resources, get the register base address */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* Mapped to a virtual address */
p_pwminfo->baseaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(p_pwminfo->baseaddr))
return PTR_ERR(p_pwminfo->baseaddr);
/* Get the clock */
p_pwminfo->p_pwmclk = devm_clk_get(&pdev->dev, CLK_NAME);
if (IS_ERR(p_pwminfo->p_pwmclk))
{
dev_err(&pdev->dev, "failed to get %s\n", CLK_NAME);
return PTR_ERR(p_pwminfo->p_pwmclk);
}
clk_rate = clk_get_rate(p_pwminfo->p_pwmclk);
/* Calculate the minimum period of the PWM */
p_pwminfo->period_min_ns = S_2_NS/clk_rate;
printk(KERN_INFO "pwm_clk: %ldHz, period_min: %dns\n", clk_rate, p_pwminfo->period_min_ns);
/* Init struct pwm_chip */
p_pwminfo->chip.dev = &pdev->dev;
p_pwminfo->chip.ops = &pwm_fops;
p_pwminfo->chip.base = 0; /* first number of the PWM's channel */
ret = of_property_read_u32(pdev->dev.of_node, "npwm", &p_pwminfo->chip.npwm);
if (ret)
{
dev_err(&pdev->dev, "failed to read npwm\n");
return ret;
}
return 0;
}
/*
* @description : Probe function of the platform, it will be executed when the
* platform driver and platform device matching successfully.
* @param -pdev : Pointer to platform device.
* @return : 0: Successful; Others: Failed.
*/
static int pwm_probe(struct platform_device *pdev)
{
int ret;
struct pwm_info *p_pwminfo;
dev_info(&pdev->dev, "Driver and device matched successfully!\n");
/* Allocate memory for struct led_info */
p_pwminfo = devm_kzalloc(&pdev->dev, sizeof(struct pwm_info), GFP_KERNEL);
if (!p_pwminfo)
return -ENOMEM;
p_pwminfo->p_dev = &pdev->dev;
/* Store the misc_info pointer in pdev->dev.driver_data for later use */
platform_set_drvdata(pdev, p_pwminfo);
/* pwm device init */
ret = pwm_init(pdev);
if (ret)
return ret;
/* Register pwm device */
ret = pwmchip_add(&p_pwminfo->chip);
if (ret < 0)
{
dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
return ret;
}
return 0 ;
}
/*
* @description : Release some resources. This function will be executed when the platform
* driver module is unloaded.
* @param : None.
* @return : 0: Successful; Others: Failed.
*/
static int pwm_remove(struct platform_device *pdev)
{
unsigned int i;
/* Get the struct misc_info pointer which is stored in pdev->dev.driver_data before */
struct pwm_info *p_pwminfo = platform_get_drvdata(pdev);
/* Some Reset code */
/* disable pwm output */
for (i=0; i<p_pwminfo->chip.npwm; i++)
writel(0, p_pwminfo->baseaddr+PWM_AXI_CTRL_REG_OFFSET);
/* Unregister the pwm device */
pwmchip_remove(&p_pwminfo->chip);
dev_info(&pdev->dev, "Driver has been removed!\n");
return 0;
}
/* Match table */
static const struct of_device_id pwm_of_match[] = {
{.compatible = COMPAT_PROPT},
{/* Sentinel */}
};
/*
* Declare device matching table. Note that this macro is generally used to dynamically
* load and unload drivers for hot-pluggable devices such as USB devices.
*/
MODULE_DEVICE_TABLE(of, pwm_of_match);
/* Platform driver struct */
static struct platform_driver pwm_driver = {
.driver = {
.name = DEVICE_NAME, //Drive name, used to match device who has the same name.
.of_match_table = pwm_of_match, //Used to match the device tree who has the same compatible property.
},
.probe = pwm_probe, //probe function
.remove = pwm_remove, //remove function
};
/*
* Register or unregister platform driver,
* and Register the entry and exit functions of the Module.
*/
module_platform_driver(pwm_driver);
/*
* Author, driver information and LICENSE.
*/
MODULE_AUTHOR("蒋楼丶");
MODULE_DESCRIPTION(DEVICE_NAME" Driver Based on Platform");
MODULE_LICENSE("GPL");