I'm completely new developing in LINUX kernel, and I'm having some problems in a new LINUX driver I'm developing.
After I map NXP PWM registers using ioremap()/ioremap_nocache() and then I try to write to the register mappend my system hags/freeze.
Could you please help me understanding what it is happening?
My Driver is this:
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h> // for threads
#include <linux/fs.h>
#include <linux/sched.h> // for task_struct
#include <linux/delay.h> // for ndelay
#include <linux/uaccess.h> // Required for the copy to user function
#include <asm/io.h> // for ioremap()
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4)
#define DEVICE_NAME "pwm_cus_drv"
#define CLASS_NAME "pwm_custom_driver"
static volatile void __iomem *mmio_pwm1_base = NULL;
static volatile void __iomem *mmio_pwm2_base = NULL;
static int majorNumber;
static struct class* vfd_char_dev_class = NULL;
static struct device* vfd_char_dev = NULL;
static struct device_driver vfd_driver;
static int dev_open(struct inode *inodep, struct file *file_ptr);
static int dev_release(struct inode *inodep, struct file *file_ptr);
static ssize_t dev_read(struct file *file_ptr, char *buffer, size_t len, loff_t *offset);
static ssize_t dev_write(struct file *file_ptr, char *buffer, size_t len, loff_t *offset);
static long dev_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg);
static irqreturn_t pwm_imx_futaba_isr(int irq, void *dev_id);
static bool Initialize_PWM_Signals(void);
static void pwm_init(void);
/**
* ISR used to attend PWM rising edge interrupt activation.
*/
irqreturn_t pwm_imx_futaba_isr(int irq, void *dev_id)
{
if(NULL != mmio_pwm1_base)
{
writel(0x00000078, mmio_pwm1_base + 0x04);
gpio_set_value(47, 1);
gpio_set_value(47, 0);
}
return IRQ_HANDLED;
}
/**
*
*/
bool Initialize_PWM_Signals(void)
{
u32 cs_pin_dir_value = 0;
u32 cs_pin_out_value = 0;
u32 duty_cycles = 0;
u32 period_cycles = 0;
u32 cr_1 = 0;
u32 cr_2 = 0;
pwm_init();
period_cycles = ((24000000)/(4000)) - 2; /* 4 KHz */
duty_cycles = period_cycles / 2; /* duty = 50% */
printk(KERN_NOTICE "PWM data. PERIOD[%d] DUTY[%d]\n", period_cycles, duty_cycles);
cr_1 = MX3_PWMCR_PRESCALER(1) | (1 << 24) | (1 << 23) | (2 << 16);
cr_2 = MX3_PWMCR_PRESCALER(1) | (1 << 24) | (1 << 23) | (2 << 16);
printk(KERN_NOTICE "Disabling IMX6UL PWMs \n");
/*******************************/
/* AFTER THIS, THE KERNEL HANGS*/
/*******************************/
writel(cr_1, mmio_pwm1_base + 0x00);
writel(cr_2, mmio_pwm2_base + 0x00);
printk(KERN_NOTICE "PWMs disabled\n");
if (1)
{
/* Configure IMX6UL PWM1 */
printk(KERN_NOTICE " Configuring PWM1 \n");
writel(duty_cycles, mmio_pwm1_base + 0x0C);
writel(period_cycles, mmio_pwm1_base + 0x10);
/* Configure IMX6UL PWM2 */
printk(KERN_NOTICE " Configuring PWM2 \n");
writel(duty_cycles, mmio_pwm2_base + 0x0C);
writel(period_cycles, mmio_pwm2_base + 0x10);
cr_1 |= (1 << 0);
cr_2 |= (1 << 0);
printk(KERN_NOTICE "Enabling IRQs !!\n");
writel(0x00000002, mmio_pwm1_base + 0x08);
/* Enabling IMX6UL PWMs */
printk(KERN_NOTICE " Enabling PWMs \n");
writel(cr_1, mmio_pwm1_base + 0x00);
writel(cr_2, mmio_pwm2_base + 0x00);
}
return 0;
}
/**
*
*/
int dev_open(struct inode *inodep, struct file *file_ptr)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
Initialize_PWM_Signals();
printk(KERN_NOTICE "[%s] Driver initialized \n", __func__);
}
/**
*
*/
int dev_release(struct inode *inodep, struct file *file_ptr)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
ssize_t dev_read(struct file *file_ptr, char *buffer, size_t len, loff_t *offset)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
ssize_t dev_write(struct file *file_ptr, char *buffer, size_t len, loff_t *offset)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
long dev_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg)
{
printk(KERN_NOTICE "\n[%s]\n", __func__);
}
/**
*
*/
void pwm_init(void)
{
printk(KERN_ALERT "[%s]\n", __func__);
if(NULL != request_mem_region(0x2080000, 0x4000, DEVICE_NAME))
{
mmio_pwm1_base = ioremap_nocache(0x2080000, 0x4000);
if(IS_ERR(mmio_pwm1_base))
{
printk(KERN_NOTICE "Failed to map memory 1\n");
}
}
else
{
printk(KERN_NOTICE "Failed to map memory 2\n");
}
if(NULL != request_mem_region(0x2084000, 0x4000, DEVICE_NAME))
{
mmio_pwm2_base = ioremap_nocache(0x2084000, 0x4000);
if(IS_ERR(mmio_pwm2_base))
{
printk(KERN_NOTICE "Failed to map memory 3\n");
}
}
else
{
printk(KERN_NOTICE "Failed to map memory 4\n");
}
printk(KERN_NOTICE "PWMs memory mapped \n");
}
static const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
.unlocked_ioctl = dev_ioctl,
.compat_ioctl = dev_ioctl,
};
struct bus_type futaba_bus_type =
{
.name = DEVICE_NAME,
};
static int pwm_driver_init(void)
{
unsigned irqflags = 0;
unsigned ret = 0;
const char *dev_name = "pwm1_irq";
u32 pwm_irq = 25;
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber < 0)
{
printk(KERN_NOTICE "EBBChar failed to register a major number\n");
return majorNumber;
}
vfd_char_dev_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(vfd_char_dev_class))
{
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_NOTICE "Failed to register device class\n");
return PTR_ERR(vfd_char_dev_class);
}
vfd_char_dev = device_create(vfd_char_dev_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(vfd_char_dev))
{
class_destroy(vfd_char_dev_class);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(vfd_char_dev);
}
ret = request_irq(pwm_irq, pwm_imx_futaba_isr, irqflags, dev_name, DEVICE_NAME);
if (0 != ret)
{
printk(KERN_NOTICE "can't get irq: %d\n", ret);
}
return 0;
}
static void pwm_driver_exit(void)
{
device_destroy(vfd_char_dev_class, MKDEV(majorNumber, 0));
class_unregister(vfd_char_dev_class);
class_destroy(vfd_char_dev_class);
unregister_chrdev(majorNumber, DEVICE_NAME);
iounmap(mmio_pwm1_base);
iounmap(mmio_pwm2_base);
}
module_init(pwm_driver_init);
module_exit(pwm_driver_exit);
MODULE_AUTHOR("New Drivers developer");
MODULE_DESCRIPTION(" PWM Handler ");
MODULE_LICENSE("GPL");
Typical reason of hang at register access is that hardware module owning the register is either powered down, or not clocked.
Per imx6ul device tree from mainline kernel (arch/arm/boot/dts/imx6ul.dtsi), there are clocks to enable:
pwm1: pwm@2080000 {
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
reg = <0x02080000 0x4000>;
interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_PWM1>,
<&clks IMX6UL_CLK_PWM1>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
status = "disabled";
};
By the way, driver for this module is available, drivers/pwm/pwm-imx.c