I.MX6 PWM buzzer driver hacking with Demo test

时间:2023-12-27 10:44:55
/*****************************************************************************
* I.MX6 PWM buzzer driver hacking with Demo test
* 声明:
* 1. I.MX6和OK335xS实现PWM驱动函数是不一样的;
* 2. 通过分析PWM驱动,了解有哪些驱动函数可以用;
* 3. 使用I.MX6提供的PWM函数,编写测试用例buzzer驱动;
* 4. 使用C编写测试程序。
*
* 2015-10-20 晴 深圳 南山平山村 曾剑锋
****************************************************************************/ \\\\\\\\\\\\\-*- 目录 -*-/////////////
| 一、cat arch/arm/plat-mxc/pwm.c
| 二、cat driver/misc/pwm_buzzer.c
| 三、cat main.c (demo test)
-------------------------------------- 一、cat arch/arm/plat-mxc/pwm.c
/*
* simple driver for PWM (Pulse Width Modulator) controller
*
* 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.
*
* Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
* Copyright 2009-2013 Freescale Semiconductor, Inc. All Rights Reserved.
*/ #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 <linux/fsl_devices.h>
#include <mach/hardware.h> /* i.MX1 and i.MX21 share the same PWM function block: */ #define MX1_PWMC 0x00 /* PWM Control Register */
#define MX1_PWMS 0x04 /* PWM Sample Register */
#define MX1_PWMP 0x08 /* PWM Period Register */ /* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ #define MX3_PWMCR 0x00 /* PWM Control Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
#define MX3_PWMPR 0x10 /* PWM Period Register */
#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4)
#define MX3_PWMCR_DOZEEN (1 << 24)
#define MX3_PWMCR_WAITEN (1 << 23)
#define MX3_PWMCR_DBGEN (1 << 22)
#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16)
#define MX3_PWMCR_CLKSRC_IPG (1 << 16)
#define MX3_PWMCR_SWR (1 << 3)
#define MX3_PWMCR_EN (1 << 0) #define MX3_PWMCR_STOPEN (1 << 25)
#define MX3_PWMCR_DOZEEN (1 << 24)
#define MX3_PWMCR_WAITEN (1 << 23)
#define MX3_PWMCR_DBGEN (1 << 22)
#define MX3_PWMCR_CLKSRC_IPG (1 << 16)
#define MX3_PWMCR_CLKSRC_IPG_32k (3 << 16) struct pwm_device {
struct list_head node;
struct platform_device *pdev; const char *label;
struct clk *clk; int clk_enabled;
void __iomem *mmio_base; unsigned int use_count;
unsigned int pwm_id;
int pwmo_invert;
void (*enable_pwm_pad)(void);
void (*disable_pwm_pad)(void);
}; /**
* 1. 操作PWM用到duty(duty_ns)、period(period_ns)2个参数;
* 2. period就是频率参数(周期时间),duty为占空比;
* 3. period和duty的参数单位为纳秒(ns);
* 4. 1s=1000ms=1000000us=1000000000ns;
* 5. period最大的取值范围为0—1000000000,而duty则取值0—period值之间;
* 6. 平时我们可能更喜欢使用频率(Hz)来表示period,占空比来表示duty;
*/
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
if (pwm == NULL || period_ns == || duty_ns > period_ns)
return -EINVAL; if (!(cpu_is_mx1() || cpu_is_mx21())) {
unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale;
u32 cr; if (pwm->pwmo_invert)
duty_ns = period_ns - duty_ns; c = clk_get_rate(pwm->clk);
c = c * period_ns;
do_div(c, );
period_cycles = c; prescale = period_cycles / 0x10000 + ; period_cycles /= prescale;
c = (unsigned long long)period_cycles * duty_ns;
do_div(c, period_ns);
duty_cycles = c; /*
* according to imx pwm RM, the real period value should be
* PERIOD value in PWMPR plus 2.
*/
if (period_cycles > )
period_cycles -= ;
else
period_cycles = ; writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR);
writel(period_cycles, pwm->mmio_base + MX3_PWMPR); cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEEN |
MX3_PWMCR_WAITEN | MX3_PWMCR_DBGEN; if (cpu_is_mx25())
cr |= MX3_PWMCR_CLKSRC_IPG;
else
cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; writel(cr, pwm->mmio_base + MX3_PWMCR);
} else if (cpu_is_mx1() || cpu_is_mx21()) {
/* The PWM subsystem allows for exact frequencies. However,
* I cannot connect a scope on my device to the PWM line and
* thus cannot provide the program the PWM controller
* exactly. Instead, I'm relying on the fact that the
* Bootloader (u-boot or WinCE+haret) has programmed the PWM
* function group already. So I'll just modify the PWM sample
* register to follow the ratio of duty_ns vs. period_ns
* accordingly.
*
* This is good enough for programming the brightness of
* the LCD backlight.
*
* The real implementation would divide PERCLK[0] first by
* both the prescaler (/1 .. /128) and then by CLKSEL
* (/2 .. /16).
*/
u32 max = readl(pwm->mmio_base + MX1_PWMP);
u32 p;
if (pwm->pwmo_invert)
duty_ns = period_ns - duty_ns;
p = max * duty_ns / period_ns;
writel(max - p, pwm->mmio_base + MX1_PWMS);
} else {
BUG();
} return ;
}
EXPORT_SYMBOL(pwm_config); /**
* 使能pwm
*/
int pwm_enable(struct pwm_device *pwm)
{
unsigned long reg;
int rc = ; if (!pwm->clk_enabled) {
rc = clk_enable(pwm->clk);
if (!rc)
pwm->clk_enabled = ;
} reg = readl(pwm->mmio_base + MX3_PWMCR);
reg |= MX3_PWMCR_EN;
writel(reg, pwm->mmio_base + MX3_PWMCR); if (pwm->enable_pwm_pad)
pwm->enable_pwm_pad(); return rc;
}
EXPORT_SYMBOL(pwm_enable); /**
* 关闭pwm
*/
void pwm_disable(struct pwm_device *pwm)
{
if (pwm->disable_pwm_pad)
pwm->disable_pwm_pad(); writel(MX3_PWMCR_SWR, pwm->mmio_base + MX3_PWMCR);
while (readl(pwm->mmio_base + MX3_PWMCR) & MX3_PWMCR_SWR)
; if (pwm->clk_enabled) {
clk_disable(pwm->clk);
pwm->clk_enabled = ;
}
}
EXPORT_SYMBOL(pwm_disable); static DEFINE_MUTEX(pwm_lock);
static LIST_HEAD(pwm_list); /**
* 通过pwm的id来表示申请pwm通道,label只是表示其名字,名字可以随意取
*/
struct pwm_device *pwm_request(int pwm_id, const char *label)
{
struct pwm_device *pwm;
int found = ; mutex_lock(&pwm_lock); list_for_each_entry(pwm, &pwm_list, node) {
if (pwm->pwm_id == pwm_id) {
found = ;
break;
}
} if (found) {
if (pwm->use_count == ) {
pwm->use_count++;
pwm->label = label;
} else
pwm = ERR_PTR(-EBUSY);
} else
pwm = ERR_PTR(-ENOENT); mutex_unlock(&pwm_lock);
return pwm;
}
EXPORT_SYMBOL(pwm_request); /**
* 前面申请了pwm,通过这个函数来释放pwm
*/
void pwm_free(struct pwm_device *pwm)
{
mutex_lock(&pwm_lock); if (pwm->use_count) {
pwm->use_count--;
pwm->label = NULL;
} else
pr_warning("PWM device already freed\n"); mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL(pwm_free); /**
* 这里是pwm控制器注册函数
*/
static int __devinit mxc_pwm_probe(struct platform_device *pdev)
{
struct pwm_device *pwm;
struct resource *r;
struct mxc_pwm_platform_data *plat_data = pdev->dev.platform_data;
int ret = ; pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
if (pwm == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
} pwm->clk = clk_get(&pdev->dev, "pwm"); if (IS_ERR(pwm->clk)) {
ret = PTR_ERR(pwm->clk);
goto err_free;
} pwm->clk_enabled = ; pwm->use_count = ;
pwm->pwm_id = pdev->id;
pwm->pdev = pdev;
if (plat_data != NULL) {
pwm->pwmo_invert = plat_data->pwmo_invert;
pwm->enable_pwm_pad = plat_data->enable_pwm_pad;
pwm->disable_pwm_pad = plat_data->disable_pwm_pad;
} r = platform_get_resource(pdev, IORESOURCE_MEM, );
if (r == NULL) {
dev_err(&pdev->dev, "no memory resource defined\n");
ret = -ENODEV;
goto err_free_clk;
} r = request_mem_region(r->start, r->end - r->start + , pdev->name);
if (r == NULL) {
dev_err(&pdev->dev, "failed to request memory resource\n");
ret = -EBUSY;
goto err_free_clk;
} pwm->mmio_base = ioremap(r->start, r->end - r->start + );
if (pwm->mmio_base == NULL) {
dev_err(&pdev->dev, "failed to ioremap() registers\n");
ret = -ENODEV;
goto err_free_mem;
} mutex_lock(&pwm_lock);
list_add_tail(&pwm->node, &pwm_list);
mutex_unlock(&pwm_lock); platform_set_drvdata(pdev, pwm);
return ; err_free_mem:
release_mem_region(r->start, r->end - r->start + );
err_free_clk:
clk_put(pwm->clk);
err_free:
kfree(pwm);
return ret;
} static int __devexit mxc_pwm_remove(struct platform_device *pdev)
{
struct pwm_device *pwm;
struct resource *r; pwm = platform_get_drvdata(pdev);
if (pwm == NULL)
return -ENODEV; mutex_lock(&pwm_lock);
list_del(&pwm->node);
mutex_unlock(&pwm_lock); iounmap(pwm->mmio_base); r = platform_get_resource(pdev, IORESOURCE_MEM, );
release_mem_region(r->start, r->end - r->start + ); clk_put(pwm->clk); kfree(pwm);
return ;
} static struct platform_driver mxc_pwm_driver = {
.driver = {
.name = "mxc_pwm",
},
.probe = mxc_pwm_probe,
.remove = __devexit_p(mxc_pwm_remove),
}; static int __init mxc_pwm_init(void)
{
return platform_driver_register(&mxc_pwm_driver);
}
arch_initcall(mxc_pwm_init); static void __exit mxc_pwm_exit(void)
{
platform_driver_unregister(&mxc_pwm_driver);
}
module_exit(mxc_pwm_exit); MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); 二、cat driver/misc/pwm_buzzer.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/fs.h> #define BUZZER_FREQENCY 1
#define DEV_NAME "buzzer" /*pwm for this buzzer*/
struct pwm_device *pwm = NULL; static int buzzer_open(struct inode *inode, struct file *filp)
{ if(pwm != NULL)
return -EBUSY; /**
* buzzer正好挂载在I.MX6的pwm4上,所以这里申请3号(从零开始算)PWM
*/
pwm = pwm_request(, "buzzer");
if ( pwm == NULL ) {
printk("buzzer open error.\n");
} return ;
} static int buzzer_release(struct inode *inode, struct file *filp)
{
pwm_disable(pwm); // 关闭pwm
pwm_free(pwm); // 释放pwm
pwm = NULL; return ;
} static long buzzer_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
if(pwm == NULL)
return -EINVAL; if(arg > || arg < )
return -EINVAL; switch (cmd) {
case BUZZER_FREQENCY:
if(arg==)
pwm_disable(pwm);
else
{
pwm_config(pwm, /arg/, /arg);
pwm_enable(pwm);
}
break;
default:
break;
} return ;
} static struct file_operations buzzer_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = buzzer_ioctl,
.open = buzzer_open,
.release = buzzer_release,
}; static struct miscdevice buzzer_miscdev =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &buzzer_fops,
}; static int __init buzzer_init(void)
{
printk(" zengjf check buzzer init.\n");
misc_register(&buzzer_miscdev);
return ;
} static void __exit buzzer_exit(void)
{
misc_deregister(&buzzer_miscdev);
} module_init(buzzer_init);
module_exit(buzzer_exit); MODULE_DESCRIPTION("pwm_buzzer driver");
MODULE_LICENSE("GPL"); 三、cat main.c (demo test)
......
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
......
int fd = open("dev/buzzer", O_RDWR);
......
// 传入频率2000Hz
ioctl(fd, PWM_IOCTL_SET_FREQ, );
......
ioctl(fd, PWM_IOCTL_STOP);
......
close(fd);