LINUX KERNEL driver hangs/freeze after handling mapped register

1.5k views Asked by At

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

There are 2 answers

0
Nikita Yushchenko On BEST ANSWER

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

0
Jonathan Guizar On

Thanks for all your answers. I solved this issue after using some NXP recommendations. At the end the problem was caused due to the IPG/PER clocks were not enabled. According NXP, clocks shall be enabled first before starting to modify registers associated with the module(in my case for PWM).