Transferred from: http://blog.csdn.net/weiqing1981127/article/details/8511676
All rights reserved, reprint must explain transfer from http://my.csdn.net/weiqing1981127
The original Nanjing University of Posts and telecommunications communication and information System Professional research two Wei Qing
A Overview of backlight back photon system
Our LCD screen often needs a backlight, adjust the brightness of the LCD screen backlight, where the backlight is not just light and not light two, but according to the needs of users, backlight brightness can be arbitrarily adjusted. Linux kernel has a backlight back photon system, the system is designed to meet the needs of users, as long as the user according to their own LCD backlight circuit PWM output pin, the kernel backlight subsystem code corresponding configuration, you can realize the LCD backlight.
LCD backlight principle is mainly by the core board of a pin control backlight power supply, a PWM pin control backlight composition, the application can change the frequency of PWM to change the backlight brightness.
Here we mainly explain the buzzer driver based on the backlight subsystem, in fact, simple to make the driver of the honey generator is very simple, here is just a buzzer as a device, and the principle of this device is similar to the backlight principle, are based on PWM, And our ultimate goal is to use the backlight back photon system. In summary, the backlight subsystem is a driver interface based on the PWM core, if you use a device is also based on PWM, and requires the user can adjust the PWM frequency to achieve such as changing the backlight brightness, change the buzzer frequency effect, Then you can use this backlight back photon system.
Two PWM Core Driver
Let's start with the PWM core.
First familiarize yourself with the following PWM core code in/ARCH/ARM/PLAT-S3C/PWM.C
View/arch/arm/plat-s3c/makefile
obj-$ (CONFIG_HAVE_PWM) + = PWM.O
View/arch/arm/plat-s3c/konfig and find no corresponding HAVE_PWM option in Konfig in the same directory
View/arch/arm/plat-s3c24xx/konfig
Config S3C24XX_PWM
BOOL "PWM Device Support"
Select HAVE_PWM
Help
Support for exporting the PWM timer blocks via the PWM device
System.
So when configuring the kernel make menuconfig, this item needs to be checked.
OK, let's see PWM.C, it's the PWM core driver, the drive does not separate the device and the drive, all written in this pwm.c, we first look at the driver part of PWM.C
static int __init pwm_init (void)
{
int ret;
Clk_scaler[0] = Clk_get (NULL, "Pwm-scaler0"); Get Clock number No. 0
CLK_SCALER[1] = Clk_get (NULL, "pwm-scaler1"); Get Clock number 1th
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); Register PWM Driver
if (ret)
PRINTK (Kern_err "%s:failed to add PWM driver\n", __func__);
return ret;
}
Track down the definition of s3c_pwm_driver
static struct Platform_driver S3c_pwm_driver = {
. Driver = {
. Name = "S3C24XX-PWM",//driver name
. Owner = This_module,
},
. Probe = S3c_pwm_probe,//Probe function
. remove = __devexit_p (S3c_pwm_remove),
};
Let's look at the detection function s3c_pwm_probe
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); Allocating PWM device Space
if (PWM = = NULL) {
Dev_err (Dev, "failed to allocate pwm_device\n");
Return-enomem;
}
Pwm->pdev = Pdev;
pwm->pwm_id = ID;
Pwm->tcon_base = id = = 0? 0: (ID * 4) + 4; Calculate which timer is controlled in the Tcon
PWM->CLK = Clk_get (Dev, "pwm-tin"); Get the clock after the Prescaler
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)) {//Get two time-divided clocks
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); Signal reversal output
__raw_writel (Tcon, S3c2410_tcon);
Local_irq_restore (flags);
ret = Pwm_register (PWM); Registering a PWM device
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;
}
Here's a look at the functions of the registered PWM device Pwm_register
Static List_head (pwm_list);
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); Attach the PWM device to the Pwm_list list
Mutex_unlock (&pwm_lock);
return 0;
}
Let's see what this pwm.c gives us. What interface functions are available?
struct Pwm_device *pwm_request (int pwm_id, const char *label)
int pwm_config (struct pwm_device *pwm, int duty_ns, int period_ns)
int pwm_enable (struct pwm_device *pwm)
void Pwm_free (struct pwm_device *pwm)
Export_symbol (pwm_request); Request a PWM device
Export_symbol (Pwm_config); Configure PWM device, Duty_ns is empty, Period_ns is cycle
Export_symbol (pwm_enable); Start Timer timer
Export_symbol (pwm_disable); Turn off timer timer
Above this function, as long as know API, will call on the line, in this, I analyze the most difficult to configure a PWM function, this function is mainly based on the period Period_ns, calculate tcnt, according to the ratio of empty to Duty_ns, calculate tcmp, and then write the corresponding register.
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;
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;
tcmp = __raw_readl (S3C2410_TCMPB (pwm->pwm_id));
tcnt = __raw_readl (S3C2410_TCNTB (pwm->pwm_id));
period = Ns_in_hz/period_ns; Calculation period
pwm_dbg (PWM, "duty_ns=%d, period_ns=%d (%lu) \ n",
Duty_ns, Period_ns, period);
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; Tcnt,n=to/ti according to the period
} else
Tin_ns = Ns_in_hz/clk_get_rate (PWM->CLK);
tcmp = Duty_ns/tin_ns; Seeking tcmp according to the ratio of empty to occupied
tcmp = tcnt-tcmp; Seeking tcmp according to duty ratio
if (tcmp = = tcnt)
tcmp--;
pwm_dbg (PWM, "Tin_ns=%lu, tcmp=%ld/%lu\n", Tin_ns, tcmp, tcnt);
if (tcmp < 0)
tcmp = 0;
Local_irq_save (flags);
__raw_writel (tcmp, S3C2410_TCMPB (pwm->pwm_id)); Write tcmp
__raw_writel (tcnt, S3C2410_TCNTB (pwm->pwm_id)); Write tcnt
Tcon = __raw_readl (S3c2410_tcon);
Tcon |= pwm_tcon_manulupdate (PWM);
Tcon |= pwm_tcon_autoreload (PWM); Auto Load
__raw_writel (Tcon, S3c2410_tcon);
Tcon &= ~pwm_tcon_manulupdate (PWM); Update tcnt and TCMP
__raw_writel (Tcon, S3c2410_tcon);
Local_irq_restore (flags);
return 0;
}
Let's talk about how this cycle is designed.
The output frequency of our timer is fi=pclk/(Prescaler value+1)/(divider value), which can be obtained by determining the value
We need to write an initial value of N to tcnt, so we can get a frequency, why?
According to the initial value n=fi/fo, then N=to/ti
So when the user passes a cycle period_ns to the Pwm_config function, it's actually To=period_ns
This n=to/ti= period_ns/fi according to the preceding formula, and then writes the initial value N to tcnt to change the cycle.
And then I'll add a note to the code comment in the Pwm_config function. What's going on with the automatic loading?
Timer working principle is actually tcnt value in the clock arrival, minus a count, each time minus one end, take the current tcnt and TCMP comparison, if the tcnt=tcmp, then the signal is vindicated to the output, and then tcnt continue to subtract a count, know tcnt minus to zero, If there is an auto-load function then the TCNTB will be the initial value of the count again to TCNTP, while TCMPB the comparative value to tcmp, so that the first initial reload, and then continue to count. We have called a double buffering mechanism for this loading mode, where TCMPB and TCNTB are buffer caches.
Front said PWM.C set drive and equipment in one, then we look at the device-related code
#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), \
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)},
};
The above code is the device part of the code, in fact, is the resources of five timers, we look at the Define_s3c_timer macro, you will find its device name is "S3C24XX-PWM", and we defined in PWM.C the driver name is also "S3C24XX-PWM", So if we register the device with the kernel, then the device "S3C24XX-PWM" and the driver "S3C24XX-PWM" will match successfully. So if you use timer 0, you can just add s3c_device_timer[0 in the BSP]. We are now doing the buzzer driver, using the TIMER0 timer, we will add the following code in the mini2440 BSP file mach-mini2440.c
static struct Platform_device *mini2440_devices[] __initdata = {
......
&S3C_DEVICE_TIMER[0],//Add
};
So we can analyze the code of the PWM core layer.
Backlight subsystem under Linux (i) "Turn"