Memory mapped I/O from Linux device driver

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Memory mapped I/O from Linux device driver

Jump to solution
9,406 Views
jon_fbrj99
Contributor I

I'm new to Linux device driver writing. Can anyone offer any advice on the proper way to access memory mapped hardware registers?

 

I'm tinkering with stuff using MCF54455EVB. This has a 7-segment LED display. So, from u-boot I can display "12" on it just by doing:

 

-> mw.l 0x09000014 0x12

 

Now I'm trying to do the same thing from a Linux device driver. According to books/online sources this should work:

 

 #define LED_REG_ADDRESS 0x09000014
  void __iomem *reg;
  request_mem_region(LED_REG_ADDRESS, 4, "LED");
  reg = ioremap_nocache(LED_REG_ADDRESS, 4);
  printk(KERN_INFO "LED phys = %x, virtual = %x\n",
     LED_REG_ADDRESS,
     (unsigned int)reg);
  writel(0x00000012, reg);

I'm assuming that ioremap_nocache should program the MMU to give a translation from kernal allocated virtual address to the actual physical register addess with no cacheing.  When it runs it prints the following, but the LED does not change.

 

 LED phys = 9000014, virtual = e1002014

 

and /proc/iomem shows me that the address has been registered. Have tried all sorts of variations on the above code, but nothing works. Anybody have any ideas?

Labels (1)
0 Kudos
Reply
1 Solution
2,642 Views
raulen
Contributor III

In order to write a value in a memory mapped register I do the following: 

 

(for instance)

#define LED_WRITE  0x90000002   /* memory mapped address */


    static unsigned char ls_leds = 0xFF;
    unsigned char *la_writeLed;

 

    la_writeLed = ioremap ((volatile unsigned long)LED_WRITE, 4);   /* use IOREMAP */

    ...

    ...   

    *(la_writeLed) = ls_leds;  /* write the desirable value in the register */

 

 

Hope this helps!

View solution in original post

0 Kudos
Reply
4 Replies
2,642 Views
ghp
Contributor I

I remember having the same problem trying to use ioremap_nocache() per the Linux device drivers books.  Finally I searched through the Freescale Coldfire kernel code and found this in arch/m68k/coldfire/mcf5445x-pci.c:

 

       /* setup FPGA to route PCI to IRQ3(67), SW7 to IRQ7, SW6 to IRQ4 */
        set_fpga(FPGA_PCI_IRQ_ENABLE, 0x00000000);
        set_fpga(FPGA_PCI_IRQ_ROUTE, 0x00000039);
        set_fpga(FPGA_SEVEN_LED, 0x000000FF);

 

The set_fpga() function is defined as an assembly routine in  arch/m68k/coldfire/head.S.  It manipulates the Access Control Register ACR0:

 

    /*
     * set_fpga(addr,val)
     *
     * Map in 0x00000000 -> 0x0fffffff and then do the write.
     */
    set_fpga:
        movew   %sr,%d1
        movew   #0x2700,%sr
        movel   ACR0_FPGA, %d0
        movec   %d0, %acr0
        nop
        moveal  4(%sp),%a0
        movel   8(%sp),%a0@
        movel   ACR0_DEFAULT, %d0
        movec   %d0, %acr0
        nop
        movew   %d1,%sr
        rts

        .data
        .align  4
 

 Using this function should allow you write to the 7-seg using the 0x09000014 physical address directly.

0 Kudos
Reply
2,643 Views
raulen
Contributor III

In order to write a value in a memory mapped register I do the following: 

 

(for instance)

#define LED_WRITE  0x90000002   /* memory mapped address */


    static unsigned char ls_leds = 0xFF;
    unsigned char *la_writeLed;

 

    la_writeLed = ioremap ((volatile unsigned long)LED_WRITE, 4);   /* use IOREMAP */

    ...

    ...   

    *(la_writeLed) = ls_leds;  /* write the desirable value in the register */

 

 

Hope this helps!

0 Kudos
Reply
2,642 Views
jon_fbrj99
Contributor I
I can now see why my original did not work. Writing directly via a pointer returned from ioremap (as shown by raulen) works. Writing the same value using writel() does not work. This is because writel is endian swapping the value! So to write 0x12345678 to a 32 bit memory mapped register I can either do *reg = 0x12345678 or writel(0x87654321, reg). Strange but true.
0 Kudos
Reply
2,642 Views
jkmahan
Contributor III
This is the quick and dirty way I did it to force a mapping that worked from just about anywhere.  Note that it also disables ints so that you don't accidentally use that mapping taking an irq.
0 Kudos
Reply