Openwrt:基于MT7628/MT7688的PWM驱动
前言
MT7628/MT7688的PWM驱动相关资料较少,官方的datasheet基本也是一堆寄存器,啃了许久,终于嚼出了味道。由于PWM存在IO口复用的问题,所以要提前配置好GPIO的工作方式,不然你无论怎么掐示波器,都不会出现预计的波形。由于MT7688和MT7628是pin to pin,几乎完全兼容,除了MT7628是2T2R而MT7688是1T1R这个区别,在PWM的设置上是相同,所以MT7688也可以直接参考MT7628的编程手册。
寄存器
pwm复用关系
MT7688最多支持四路PWM,分别是pwm_ch0、pwm_ch1、pwm_ch2、pwm_ch3,从MT7688的pin map可知:
- pwm_ch0、pwm_ch1复用在PAD_MDI_TP_P1和PAD_MDI_TN_P1上,也就是网口1的传输线,再往下看,pwm_ch0、pwm_ch1还复用在txd1、rxd1上,这是串口1;
- pwm_ch2、pwm_ch3复用在PAD_MDI_TP_P2和PAD_MDI_TN_P2上,也就是网口2;
由上面如果既要使用网口1的功能,又要使用pwm_ch0、pwm_ch1的功能,那么必须复用到txd1和rxd1上才可以,因为系统默认GPIO14、15的功能是网口1,而GPIO45、46的功能是串口1,所以这里还需对GPIO的模式进行配置,同理pwm_ch2、pwm_ch3也需要进行类似的配置。
gpio mode
选择那几路pwm做为这几个GPIO的功能则需要配置GPIO_MODE寄存器了,由下图可知,GPIO被分为两组,分别为GPIO1与GPIO2,寄存器地址分别为10000060和10000064这在后面驱动编写对GPIO进行初始化的时候会用到。 
下图是GPIO1的寄存器配置方式,在此可知,对于PWM的选择只需配置相应的Bit即可。不同的模式通过不同的值进行选择。 
代码
以下驱动代码参考了小鱼儿专栏的博文,做了部分修改,修复了几处错误,经测试可以使用。
mt7688_pwm.c
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/spinlock.h>  
#include "mt7688_pwm.h"
MODULE_LICENSE("GPL");  
#define RALINK_CLK_CFG    0xB0000030
#define RALINK_AGPIO_CFG  0xB000003C
#define RALINK_GPIOMODE   0xB0000060   
#define RALINK_PWM_BASE   0xB0005000
//#define RALINK_CLK_CFG    0x10000030
//#define RALINK_AGPIO_CFG  0x1000003C
//#define RALINK_GPIOMODE   0x10000060
//#define RALINK_PWM_BASE   0x10005000
#define RALINK_PWM_ENABLE    RALINK_PWM_BASE   
#define PWM_MODE_BIT      15
#define PWM_GVAL_BIT      8
#define PWM_IVAL_BIT      7    
enum {
    PWM_REG_CON,
    PWM_REG_GDUR = 0x0C,
    PWM_REG_WNUM = 0x28,
    PWM_REG_DWID = 0x2C,
    PWM_REG_THRE = 0x30,
    PWM_REG_SNDNUM = 0x34,
}PWM_REG_OFF;  
#define PWM_NUM  4
u32 PWM_REG[PWM_NUM] = {
    (RALINK_PWM_BASE + 0x10),    /* pwm0 base */
    (RALINK_PWM_BASE + 0x50),    /* pwm1 base */
    (RALINK_PWM_BASE + 0x90),    /* pwm2 base */
    (RALINK_PWM_BASE + 0xD0)     /* pwm3 base */
};  
#define NAME  "sooall_pwm"  
int pwm_major;
int pwm_minor  = 0;
int pwm_device_cnt = 1;
struct cdev pwm_cdev;  
static struct class  *pwm_class;
static struct device *pwm_device;  
spinlock_t pwm_lock;  
static void sooall_pwm_cfg(struct pwm_cfg *cfg)
{
    u32 value;
    unsigned long  flags;
    u32 basereg;  
    basereg = PWM_REG[cfg->no];  
    spin_lock_irqsave(&pwm_lock, flags);  
    /* 1. set the pwm control register */
    value  = le32_to_cpu(*(volatile u32 *)(basereg + PWM_REG_CON));
    /* old mode */
    value |= (1 << PWM_MODE_BIT);
    /* set the idel val and guard val */
    value &= ~((1 << PWM_IVAL_BIT) | (1 << PWM_GVAL_BIT));
    value |= ((cfg->idelval & 0x1) << PWM_IVAL_BIT);
    value |= ((cfg->guardval & 0x1) << PWM_GVAL_BIT);
    /* set the source clk */
    if (cfg->clksrc == PWM_CLK_100KHZ) {
        value &= ~(1<<3);
    } else {
        value |= (1<<3);
    }
    /* set the clk div */
    value &= ~0x7;
    value |= (0x7 & cfg->clkdiv);
    *(volatile u32 *)(basereg + PWM_REG_CON) = cpu_to_le32(value);    
    /* 2. set the guard duration val */
    value  = le32_to_cpu(*(volatile u32 *)(basereg + PWM_REG_GDUR));
    value &= ~(0xffff);
    value |= (cfg->guarddur & 0xffff);
    *(volatile u32 *)(basereg + PWM_REG_GDUR) = cpu_to_le32(value);   
    /* 3. set the wave num val */
    value  = le32_to_cpu(*(volatile u32 *)(basereg + PWM_REG_WNUM));
    value &= ~(0xffff);
    value |= (cfg->wavenum & 0xffff);
    *(volatile u32 *)(basereg + PWM_REG_WNUM) = cpu_to_le32(value);   
    /* 4. set the data width val */
    value  = le32_to_cpu(*(volatile u32 *)(basereg + PWM_REG_DWID));
    value &= ~(0x1fff);
    value |= (cfg->datawidth & 0x1fff);
    *(volatile u32 *)(basereg + PWM_REG_DWID) = cpu_to_le32(value);   
    /* 5. set the thresh  val */
    value  = le32_to_cpu(*(volatile u32 *)(basereg + PWM_REG_THRE));
    value &= ~(0x1fff);
    value |= (cfg->threshold & 0x1fff);
    *(volatile u32 *)(basereg + PWM_REG_THRE) = cpu_to_le32(value);   
    spin_unlock_irqrestore(&pwm_lock, flags);
}  
static void sooall_pwm_enable(int no)
{
    u32 value;
    unsigned long  flags;  
    printk(KERN_INFO NAME "enable pwm%d\n", no);  
    spin_lock_irqsave(&pwm_lock, flags);
    value  = le32_to_cpu(*(volatile u32 *)(RALINK_PWM_ENABLE));
    value  |= (1 << no);
    *(volatile u32 *)(RALINK_PWM_ENABLE) = cpu_to_le32(value);
    spin_unlock_irqrestore(&pwm_lock, flags);
}  
static void sooall_pwm_disable(int no)
{
    u32 value;
    unsigned long  flags;  
    printk(KERN_INFO NAME "disable pwm%d\n", no);  
    spin_lock_irqsave(&pwm_lock, flags);
    value  = le32_to_cpu(*(volatile u32 *)(RALINK_PWM_ENABLE));
    value  &= ~(1 << no);
    *(volatile u32 *)(RALINK_PWM_ENABLE) = cpu_to_le32(value);
    spin_unlock_irqrestore(&pwm_lock, flags);
}
static void sooall_pwm_getsndnum(struct pwm_cfg *cfg)
{
    u32 value;
    unsigned long  flags;
    u32 regbase = PWM_REG[cfg->no];  
    spin_lock_irqsave(&pwm_lock, flags);
    value  = le32_to_cpu(*(volatile u32 *)(regbase + PWM_REG_SNDNUM));
    cfg->wavenum = value;
    spin_unlock_irqrestore(&pwm_lock, flags);
}  
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,35)
long sooall_pwm_ioctl(struct file *file, unsigned int req,
        unsigned long arg)
#else
int sooall_pwm_ioctl(struct inode *inode, struct file *file, unsigned int req,
        unsigned long arg)
#endif
{
    switch (req) {
    case PWM_ENABLE:
        sooall_pwm_enable(((struct pwm_cfg *)arg)->no);
        break;
    case PWM_DISABLE:
        sooall_pwm_disable(((struct pwm_cfg *)arg)->no);
        break;
    case PWM_CONFIGURE:
        sooall_pwm_cfg((struct pwm_cfg *)arg);
        break;
    case PWM_GETSNDNUM:
        sooall_pwm_getsndnum((struct pwm_cfg *)arg);
        break;
    default:
        return -ENOIOCTLCMD;
    }  
    return 0;
}  
static int sooall_pwm_open(struct inode * inode, struct file * filp)
{
    return 0;
}  
static int sooall_pwm_close(struct inode *inode, struct file *file)
{
    return 0;
}  
static const struct file_operations pwm_fops = {
    .owner      = THIS_MODULE,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,35)
    unlocked_ioctl:sooall_pwm_ioctl,
#else
    ioctl:sooall_pwm_ioctl,
#endif
    .open       = sooall_pwm_open,
    .release    = sooall_pwm_close,
};  
static int setup_chrdev(void)
{
    dev_t dev;
    int err = 0;  
    if (pwm_major) {
        dev = MKDEV(pwm_major, 0);
        err = register_chrdev_region(dev, pwm_device_cnt, NAME);
    } else {
        err = alloc_chrdev_region(&dev, 0, pwm_device_cnt, NAME);
        pwm_major = MAJOR(dev);
    }  
    if (err < 0) {
        printk(KERN_ERR NAME "get device number failed\n");
        return -1;
    }  
    cdev_init(&pwm_cdev, &pwm_fops);
    pwm_cdev.owner = THIS_MODULE;
    pwm_cdev.ops = &pwm_fops;
    err = cdev_add(&pwm_cdev, dev, pwm_device_cnt);
    if (err < 0) {
        printk(KERN_ERR NAME "cdev_add failed\n");
        unregister_chrdev_region(dev, pwm_device_cnt);
        return -1;
    }  
    return 0;
}  
static void clean_chrdev(void)
{
    dev_t dev = MKDEV(pwm_major, 0);  
    cdev_del(&pwm_cdev);
    unregister_chrdev_region(dev, pwm_device_cnt);
}  
static void setup_gpio(void)
{
    u32 value;
    int i = 0;
    /* pwm0 pwm1 */  
    /* enable the pwm clk */
    value  = le32_to_cpu(*(volatile u32 *)(RALINK_CLK_CFG));
    value  |= (1 << 31);
    *(volatile u32 *)(RALINK_CLK_CFG) = cpu_to_le32(value);   
    /* set the agpio cfg of ephy_gpio_aio_en */
    //value  = le32_to_cpu(*(volatile u32 *)(RALINK_AGPIO_CFG));
    //value  |= (0xF<<17);
    //*(volatile u32 *)(RALINK_AGPIO_CFG) = cpu_to_le32(value);     
    /* set the pwm  mode */
    //value  = le32_to_cpu(*(volatile u32 *)(RALINK_GPIOMODE));
    //value  &= ~(3 << 28 | 3 << 30);
    //*(volatile u32 *)(RALINK_GPIOMODE) = cpu_to_le32(value);      
   value  = le32_to_cpu(*(volatile u32 *)(RALINK_GPIOMODE));
   value  |= (2 << 24);  
   *(volatile u32 *)(RALINK_GPIOMODE) = cpu_to_le32(value);         
    /* disable all the pwm */
    for (i = 0; i < PWM_NUM; i++) {
        sooall_pwm_disable(i);
    }
}  
static int sooall_pwm_init(void)
{
    int ret = 0;  
    spin_lock_init(&pwm_lock);  
    ret = setup_chrdev();
    if (ret < 0)
        return -1;  
    pwm_class = class_create(THIS_MODULE, NAME);
    if (NULL == pwm_class) {
        printk(KERN_ERR NAME "class_create failed\n");
        goto dev_clean;
    }  
    pwm_device = device_create(pwm_class, NULL,
                    MKDEV(pwm_major, pwm_minor), NULL, "sooall_pwm");
    if (NULL == pwm_device) {
        printk(KERN_ERR NAME "device_create failed\n");
        goto class_clean;
    }  
    setup_gpio();     
    printk(KERN_INFO "sooall pwm init success\n");
    return 0;  
class_clean:
    class_destroy(pwm_class);
dev_clean:
    clean_chrdev();
    return -1;
}  
static void sooall_pwm_exit(void)
{
    device_destroy(pwm_class, MKDEV(pwm_major, pwm_minor));
    class_destroy(pwm_class);
    clean_chrdev();
    printk(KERN_INFO "sooall pwm exit\n");
}  
module_init(sooall_pwm_init);
module_exit(sooall_pwm_exit);
mt7688_pwm.h
#ifndef _SOOALL_PWM_H_
#define _SOOALL_PWM_H_  
/* the source of the clock */
typedef enum {
    PWM_CLK_100KHZ,
    PWM_CLK_40MHZ
}PWM_CLK_SRC;  
/* clock div */
typedef enum {
    PWM_CLI_DIV0 = 0,
    PWM_CLK_DIV2,
    PWM_CLK_DIV4,
    PWM_CLK_DIV8,
    PWM_CLK_DIV16,
    PWM_CLK_DIV32,
    PWM_CLK_DIV64,
    PWM_CLK_DIV128,
}PWM_CLK_DIV;  
struct pwm_cfg {
    int no;
    PWM_CLK_SRC    clksrc;
    PWM_CLK_DIV    clkdiv;
    unsigned char  idelval;
    unsigned char  guardval;
    unsigned short guarddur;
    unsigned short wavenum;
    unsigned short datawidth;
    unsigned short threshold;
};  
/* ioctl */
#define PWM_ENABLE      0
#define PWM_DISABLE     1
#define PWM_CONFIGURE   2
#define PWM_GETSNDNUM   3
#endif
How to use it?
下载源码 
压缩包中包含两个压缩包分别是mt7688_pwm.tar.gz、mt7688_pwm_app.tar.gz,驱动和测试程序。 
并解压缩到openwrt 15.01 SDK的Package/下。进行编译即可。 
最后附一张成功的图片^_^ 
Openwrt:基于MT7628/MT7688的PWM驱动的更多相关文章
- 基于ARM-contexA9-蜂鸣器pwm驱动开发
		上次,我们写过一个蜂鸣器叫的程序,但是那个程序仅仅只是驱动蜂鸣器,用电平1和0来驱动而已,跟驱动LED其实没什么两样.我们先来回顾一下蜂鸣器的硬件还有相关的寄存器吧: 还是和以前一样的步骤: 1.看电 ... 
- Linux系统PWM驱动【转】
		本文转载自:https://blog.csdn.net/BorntoX/article/details/51879786 硬件平台:IMX6 内核版本:kernel3.0.35 在linux内核中有一 ... 
- 基于Mongodb的轻量级领域驱动框架(序)
		混园子也有些年头了,从各个大牛那儿学了很多东西.技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告).转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开 ... 
- 基于335X的UBOOT网口驱动分析
		基于335X的UBOOT网口驱动分析 一.软硬件平台资料 1. 开发板:创龙AM3359核心板,网口采用RMII形式 2. UBOOT版本:U-Boot-2016.05,采用FDT和DM. 参考链 ... 
- linux驱动编写(pwm驱动)【转】
		本文转载自:https://blog.csdn.net/feixiaoxing/article/details/79889240 pwm方波可以用来控制很多的设备,比如它可以被用来控制电机.简单来说, ... 
- 基于STM32F4移植W5500官方驱动库ioLibrary_Driver(转)
		源: 基于STM32F4移植W5500官方驱动库ioLibrary_Driver 参考: 基于STM32+W5500 的Ethernet和Internet移植 Upgrade W5500 Throug ... 
- linux驱动开发—基于Device tree机制的驱动编写
		前言Device Tree是一种用来描述硬件的数据结构,类似板级描述语言,起源于OpenFirmware(OF).在目前广泛使用的Linux kernel 2.6.x版本中,对于不同平台.不同硬件,往 ... 
- 基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读
		本文将介绍看门狗驱动的实现. 目标平台:TQ2440 CPU:s3c2440 内核版本:2.6.30 1. 看门狗概述 看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如 ... 
- 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
		基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl 0. 导语 在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后 ... 
随机推荐
- Python中有许多HTTP客户端,但使用最广泛且最容易的是requests
			前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:北京尚脑软件测试 PS:如有需要Python学习资料的小伙伴可以加点击 ... 
- BUG 测试计划
			性能追求 目前状况 测试标准 APP平稳运行,无crush现象 快速下拉翻页时,崩溃退出 要求多人使用,均流畅无异常退出方可 页面的放大缩小不会造成页面显 ... 
- Cobalt Strike系列教程第七章:提权与横向移动
			Cobalt Strike系列教程分享如约而至,新关注的小伙伴可以先回顾一下前面的内容: Cobalt Strike系列教程第一章:简介与安装 Cobalt Strike系列教程第二章:Beacon详 ... 
- RedHat下安装Ecshop
			1. 设置虚拟机 2. 创建文件夹 mkdir /mnt/cdrom 3. 加载LINUX安装光盘 mount /dev/cdrom /mnt/cdrom 4. 进入安装程序目录 cd /mnt/cd ... 
- 零基础的学习者应该怎么开始学习呢?Python核心知识学习思维分享
			近几年,Python一路高歌猛进,成为最受欢迎的编程语言之一,受到无数编程工作者的青睐. 据悉,Python已经入驻部分小学生教材,可以预见学习Python将成为一项提高自身职业竞争力的必修课.那么零 ... 
- 【题解】P2831 愤怒的小鸟 - 状压dp
			P2831愤怒的小鸟 题目描述 \(Kiana\) 最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于 \((0,0)\) 处,每次 \(Kiana\) 可以 ... 
- 在php中如何实现cookie即时生效,不用刷新就可以使用
			参考:https://www.jianshu.com/p/0468ef5dbf4d 今天在用php设置cookie的时候,发现cookie如果只是赋值一次的话,要手动刷新一下浏览器才能把数据及时更新, ... 
- 通过纯css实现图片居中的多种实现方式
			html结构: <div class="demo" style="width: 800px;height: 600px; border:1px solid #ddd ... 
- 使用sys模块写一个软件安装进度条
			import sys,time for i in range(50): sys.stdout.write('#') sys.stdout.flush() #强制刷新将内存中的文件写一条,输出一条. t ... 
- dhcpd.conf(5) - Linux man page
			http://linux.die.net/man/5/dhcpd.conf Name dhcpd.conf - dhcpd configuration file Description The d ... 
