前言

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驱动的更多相关文章

  1. 基于ARM-contexA9-蜂鸣器pwm驱动开发

    上次,我们写过一个蜂鸣器叫的程序,但是那个程序仅仅只是驱动蜂鸣器,用电平1和0来驱动而已,跟驱动LED其实没什么两样.我们先来回顾一下蜂鸣器的硬件还有相关的寄存器吧: 还是和以前一样的步骤: 1.看电 ...

  2. Linux系统PWM驱动【转】

    本文转载自:https://blog.csdn.net/BorntoX/article/details/51879786 硬件平台:IMX6 内核版本:kernel3.0.35 在linux内核中有一 ...

  3. 基于Mongodb的轻量级领域驱动框架(序)

    混园子也有些年头了,从各个大牛那儿学了很多东西.技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告).转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开 ...

  4. 基于335X的UBOOT网口驱动分析

    基于335X的UBOOT网口驱动分析 一.软硬件平台资料 1.  开发板:创龙AM3359核心板,网口采用RMII形式 2.  UBOOT版本:U-Boot-2016.05,采用FDT和DM. 参考链 ...

  5. linux驱动编写(pwm驱动)【转】

    本文转载自:https://blog.csdn.net/feixiaoxing/article/details/79889240 pwm方波可以用来控制很多的设备,比如它可以被用来控制电机.简单来说, ...

  6. 基于STM32F4移植W5500官方驱动库ioLibrary_Driver(转)

    源: 基于STM32F4移植W5500官方驱动库ioLibrary_Driver 参考: 基于STM32+W5500 的Ethernet和Internet移植 Upgrade W5500 Throug ...

  7. linux驱动开发—基于Device tree机制的驱动编写

    前言Device Tree是一种用来描述硬件的数据结构,类似板级描述语言,起源于OpenFirmware(OF).在目前广泛使用的Linux kernel 2.6.x版本中,对于不同平台.不同硬件,往 ...

  8. 基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读

    本文将介绍看门狗驱动的实现. 目标平台:TQ2440 CPU:s3c2440 内核版本:2.6.30 1. 看门狗概述 看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如 ...

  9. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl 0. 导语 在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后 ...

随机推荐

  1. 不借助多余参数也可交换两个参数(c++,swap函数)

    利用a^a=0异或属性 [示例代码] #include<stdio.h> void data_swap(int &a,int &b){ a = a ^ b; b = a ^ ...

  2. DataTable运用

    DataTable dataDis.AsEnumerable().Sum(bu => bu["QtyPlan"].ConvertInt32()); ndata.TDefSty ...

  3. Java 反射 -- 获取泛型类型

    先写一个类: public class Demo03 { public void test01(Map<String, User> map, List<User> list) ...

  4. HDFS一些基本操作方法

    启动hadoop cd /usr/local/hadoop ./sbin/start-dfs.sh 在浏览器中打开localhost:50070 找到 进入  操作 1)新建文件夹      在根目录 ...

  5. nginx history路由模式时,页面返回404重定向index.html

    1.路由默认是带#的,有时我们感觉不美观,就使其变为history模式,也就没有#字符 2.# 如果找不到当前页面(404),就返回index.html,重新分配路由 location ^~/prod ...

  6. metaspliot(一)

    来自山丘安全实验室 陈毅 https://www.cnblogs.com/sec875/articles/12243725.html linux下载与更新 apt-get update apt-get ...

  7. 2019-2020-1 20199308《Linux内核原理与分析》第五周作业

    <Linux内核分析> 第四章 系统调用的三层机制(上) 4.1 用户态.内核态和中断 与系统调用打交道的方式是通过库函数的方式 用户态与内核态的区分 内核态:高的执行级别下,代码可以执行 ...

  8. linux抓包的实现

    工具: wireshark tcpdump 在这里仅仅介绍后者: 在网络问题的调试中,tcpdump应该说是一个必不可少的工具,和大部分linux下优秀工具一样,它的特点就是简单而强大. 默认情况下, ...

  9. cut,xargs,sort,tr,rename命令解析

    cut 文件内容查看 显示行中的指定部分,删除文件中指定字段 显示文件的内容,类似于下的type命令. 语法: cut(选项)(参数) 选项: -b:仅显示行中指定直接范围的内容: -c:仅显示行中指 ...

  10. LaTex中文article模板(支持代码、数学、TikZ)

    代码 请使用XeLatex编译 main.tex \documentclass{article} \usepackage{ctex} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ...