linux-kernellinux-device-driverioremap

LINUX KERNEL driver hangs/freeze after handling mapped register


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");

Solution

  • 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