解决了一直以来困扰的问题——基于内核提供的pwm驱动框架申请pwm设备失败(提示没有相应的文档或文件-NOENT)

pwm驱动框架提供的函数有:

struct pwm_device;  
  
/* 
 * pwm_request - request a PWM device 
 */  
struct pwm_device *pwm_request(int pwm_id, const char *label);  
  
/* 
 * pwm_free - free a PWM device 
 */  
void pwm_free(struct pwm_device *pwm);  
  
/* 
 * pwm_config - change a PWM device configuration 
 */  
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);  
  
/* 
 * pwm_enable - start a PWM output toggling 
 */  
int pwm_enable(struct pwm_device *pwm);  
  
/* 
 * pwm_disable - stop a PWM output toggling 
 */  
void pwm_disable(struct pwm_device *pwm);  

struct pwm_device结构体为:

struct pwm_device {  
    struct list_head     list;  
    struct platform_device  *pdev;  
  
    const char      *label;  
  
    unsigned int         period_ns;  
    unsigned int         duty_ns;  
  
    unsigned char        running;  
    unsigned char        use_count;  
    unsigned char        pwm_id;  
};  

在内核的pwm驱动框架中,struct pwm_device结构体以链表的形式存在,pwm的链表形成是基于平台总线模式下的。

具体过程为:先声明与pwm有关的定时器的资源,就是把中断号保存在资源中,供给驱动匹配后申请中断使用和时钟获取。

/* Standard setup for a timer block. */

#define TIMER_RESOURCE_SIZE (1)

#define TIMER_RESOURCE(_tmr, _irq)			\
	(struct resource [TIMER_RESOURCE_SIZE]) {	\
		[0] = {					\
			.start	= _irq,			\
			.end	= _irq,			\
			.flags	= IORESOURCE_IRQ	\
		}					\
	}

#define DEFINE_S3C_TIMER(_tmr_no, _irq)			\
	.name		= "s3c24xx-pwm",		\
	.id		= _tmr_no,			\
	.num_resources	= TIMER_RESOURCE_SIZE,		\
	.resource	= TIMER_RESOURCE(_tmr_no, _irq),	\

/* since we already have an static mapping for the timer, we do not
 * bother setting any IO resource for the base.
 */

struct platform_device s3c_device_timer[] = {
	[0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },
	[1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },
	[2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },
	[3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },
	[4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },
};
int platform_add_devices(struct platform_device * * devs, int num)

platform_add_devices实现了s3c_device_timer[]平台数据的注册,这样在总线上就会有相关的设备。

static struct platform_driver s3c_pwm_driver = {
	.driver		= {
		.name	= "s3c24xx-pwm",
		.owner	= THIS_MODULE,
	},
	.probe		= s3c_pwm_probe,
	.remove		= __devexit_p(s3c_pwm_remove),
};

s3c_pwm_driver 使用.name    = "s3c24xx-pwm",与

#define DEFINE_S3C_TIMER(_tmr_no, _irq)			\
	.name		= "s3c24xx-pwm",		\
	.id		= _tmr_no,			\
	.num_resources	= TIMER_RESOURCE_SIZE,		\
	.resource	= TIMER_RESOURCE(_tmr_no, _irq),	\

中的名字匹配,要记住的是id只是为了区分同类设备,因为多个设备要匹配一个驱动就要靠名字来匹配,所以名字就要设置成为一样的,而在sys文件中为了生成管理的对应文件,就要用id来区分,所以id仅仅用于设备之间来区分,不用于匹配。

static int __init pwm_init(void)
{
	int ret;

	clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
	clk_scaler[1] = clk_get(NULL, "pwm-scaler1");

	if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
		printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
		return -EINVAL;
	}

	ret = platform_driver_register(&s3c_pwm_driver);
	if (ret)
		printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);

	return ret;
}

arch_initcall(pwm_init);

获取时钟,    clk_scaler[0] = clk_get(NULL, "pwm-scaler0");

注册驱动:ret = platform_driver_register(&s3c_pwm_driver);

这次学到的重要知识:arch_initcall(pwm_init);是在系统启动时内核调用的,自动运行,不需要自己加载。所以在编写驱动的时候如果要自动加载的话,可以使用arch_initcall(入口函数名)。

 

static int s3c_pwm_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct pwm_device *pwm;
	unsigned long flags;
	unsigned long tcon;
	unsigned int id = pdev->id;
	int ret;

	if (id == 4) {
		dev_err(dev, "TIMER4 is currently not supported\n");
		return -ENXIO;
	}

	pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
	if (pwm == NULL) {
		dev_err(dev, "failed to allocate pwm_device\n");
		return -ENOMEM;
	}

	pwm->pdev = pdev;
	pwm->pwm_id = id;

	/* calculate base of control bits in TCON */
	pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;

	pwm->clk = clk_get(dev, "pwm-tin");
	if (IS_ERR(pwm->clk)) {
		dev_err(dev, "failed to get pwm tin clk\n");
		ret = PTR_ERR(pwm->clk);
		goto err_alloc;
	}

	pwm->clk_div = clk_get(dev, "pwm-tdiv");
	if (IS_ERR(pwm->clk_div)) {
		dev_err(dev, "failed to get pwm tdiv clk\n");
		ret = PTR_ERR(pwm->clk_div);
		goto err_clk_tin;
	}

	local_irq_save(flags);

	tcon = __raw_readl(S3C2410_TCON);
	tcon |= pwm_tcon_invert(pwm);
	__raw_writel(tcon, S3C2410_TCON);

	local_irq_restore(flags);


	ret = pwm_register(pwm);
	if (ret) {
		dev_err(dev, "failed to register pwm\n");
		goto err_clk_tdiv;
	}

	pwm_dbg(pwm, "config bits %02x\n",
		(__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);

	dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
		 clk_get_rate(pwm->clk),
		 clk_get_rate(pwm->clk_div),
		 pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);

	platform_set_drvdata(pdev, pwm);
	return 0;

 err_clk_tdiv:
	clk_put(pwm->clk_div);

 err_clk_tin:
	clk_put(pwm->clk);

 err_alloc:
	kfree(pwm);
	return ret;
}

static int s3c_pwm_remove(struct platform_device *pdev)
{
	struct pwm_device *pwm = platform_get_drvdata(pdev);

	clk_put(pwm->clk_div);
	clk_put(pwm->clk);
	kfree(pwm);

	return 0;
}

probe函数和remove函数实现了pwm设备的初始化和加载进入链表的过程,还包括时钟定时器的获取。

static int pwm_register(struct pwm_device *pwm)
{
	pwm->duty_ns = -1;
	pwm->period_ns = -1;

	mutex_lock(&pwm_lock);
	list_add_tail(&pwm->list, &pwm_list);
	mutex_unlock(&pwm_lock);

	return 0;
}

获取时钟的API接口

/* Clock API calls */

struct clk *clk_get(struct device *dev, const char *id)
{
	struct clk *p;
	struct clk *clk = ERR_PTR(-ENOENT);
	int idno;

	if (dev == NULL || dev->bus != &platform_bus_type)
		idno = -1;
	else
		idno = to_platform_device(dev)->id;

	spin_lock(&clocks_lock);

	list_for_each_entry(p, &clocks, list) {
		if (p->id == idno &&
		    strcmp(id, p->name) == 0 &&
		    try_module_get(p->owner)) {
			clk = p;
			break;
		}
	}

	/* check for the case where a device was supplied, but the
	 * clock that was being searched for is not device specific */

	if (IS_ERR(clk)) {
		list_for_each_entry(p, &clocks, list) {
			if (p->id == -1 && strcmp(id, p->name) == 0 &&
			    try_module_get(p->owner)) {
				clk = p;
				break;
			}
		}
	}

	spin_unlock(&clocks_lock);
	return clk;
}

综上所述:pwm的内核驱动框架还是基于平台设备总线实现的,接入了链表管理多个pwm的申请于配置。

 


struct pwm_device *pwm_request(int pwm_id, const char *label)
{
	struct pwm_device *pwm;
	int found = 0;

	mutex_lock(&pwm_lock);

	list_for_each_entry(pwm, &pwm_list, list) {
		if (pwm->pwm_id == pwm_id) {
			found = 1;
			break;
		}
	}

	if (found) {
		if (pwm->use_count == 0) {
			pwm->use_count = 1;
			pwm->label = label;
		} else
			pwm = ERR_PTR(-EBUSY);
	} else
		pwm = ERR_PTR(-ENOENT);

	mutex_unlock(&pwm_lock);
	return pwm;
}

EXPORT_SYMBOL(pwm_request);


void pwm_free(struct pwm_device *pwm)
{
	mutex_lock(&pwm_lock);

	if (pwm->use_count) {
		pwm->use_count--;
		pwm->label = NULL;
	} else
		printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id);

	mutex_unlock(&pwm_lock);
}

有申请函数和释放函数可以看出,pwm可以被多个驱动申请,pwm结构体使用count计量申请的次数,在释放时恢复原来的计数。

 

	pwm->clk = clk_get(dev, "pwm-tin");
	if (IS_ERR(pwm->clk)) {
		dev_err(dev, "failed to get pwm tin clk\n");
		ret = PTR_ERR(pwm->clk);
		goto err_alloc;
	}

	pwm->clk_div = clk_get(dev, "pwm-tdiv");
	if (IS_ERR(pwm->clk_div)) {
		dev_err(dev, "failed to get pwm tdiv clk\n");
		ret = PTR_ERR(pwm->clk_div);
		goto err_clk_tin;
	}

获取了两个时钟,在pwm_config函数里面使用。是pwm的两种不同工作模式所需要不同的时钟。


int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
	unsigned long tin_rate;
	unsigned long tin_ns;
	unsigned long period;
	unsigned long flags;
	unsigned long tcon;
	unsigned long tcnt;
	long tcmp;

	/* We currently avoid using 64bit arithmetic by using the
	 * fact that anything faster than 1Hz is easily representable
	 * by 32bits. */

	if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
		return -ERANGE;

	if (duty_ns > period_ns)
		return -EINVAL;

	if (period_ns == pwm->period_ns &&
	    duty_ns == pwm->duty_ns)
		return 0;

	/* The TCMP and TCNT can be read without a lock, they're not
	 * shared between the timers. */

	tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));
	tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));

	period = NS_IN_HZ / period_ns;

	pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",
		duty_ns, period_ns, period);

	/* Check to see if we are changing the clock rate of the PWM */

	if (pwm->period_ns != period_ns) {
		if (pwm_is_tdiv(pwm)) {
			tin_rate = pwm_calc_tin(pwm, period);
			clk_set_rate(pwm->clk_div, tin_rate);
		} else
			tin_rate = clk_get_rate(pwm->clk);

		pwm->period_ns = period_ns;

		pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);

		tin_ns = NS_IN_HZ / tin_rate;
		tcnt = period_ns / tin_ns;
	} else
		tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);

	/* Note, counters count down */

	tcmp = duty_ns / tin_ns;
	tcmp = tcnt - tcmp;

	pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);

	if (tcmp < 0)
		tcmp = 0;

	/* Update the PWM register block. */

	local_irq_save(flags);

	__raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));
	__raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));

	tcon = __raw_readl(S3C2410_TCON);
	tcon |= pwm_tcon_manulupdate(pwm);
	tcon |= pwm_tcon_autoreload(pwm);
	__raw_writel(tcon, S3C2410_TCON);

	tcon &= ~pwm_tcon_manulupdate(pwm);
	__raw_writel(tcon, S3C2410_TCON);

	local_irq_restore(flags);

	return 0;
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值