This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM1808 - Accessing FPGA from Linux C Code

Other Parts Discussed in Thread: AM1808, CODECOMPOSER, OMAP-L138, DA8XX

I am having difficulty accessing FPGA from an application written in C that is executed under Ubuntu Linux.  I am using the AM1808 processor and I have an oscilliscope probe on the Chip Select pin.  I am working with a custom board that was designed with the same hardware pieces as that was found on the Experimenter Board from ZOOM.  (i.e same NAND chip and processor).  What I have found is the following - I have followed the instructions on how to activate EMIFA and have setup the PIN MUX, PLL and PSC appropriately.  I say appropriately because I have been able to see activity on the oscilloscope when I write to the FPGA memory location of 0x66000000 using u-boot mw command as well as when I forcibly change the memory location using CodeComposer with the Blackhawk USB100v2 emulator. In my Linux setup, however, I am unable to see any activity when I try to write to the memory location.  I have even tried setting up the PINMUX registers directly in my C code followed by the attempt to write to the 0x6600000 memory location (FYI - we are using Chip Select 5 on our custom board for FPGA).  Here is how I am trying to access the FPGA memory from my C code:

...
unsigned short *la_writeTest;
la_writeTest = ioremap((volatile unsigned long)0x66000000,4);
*(la_writeTest) = 0x3333;
printk(KERN_INFO "66000000" = %x", (unsigned short)*la_writeTest);
...

Now...the printk actually shows the value that was set...but there is no activity on the oscilloscope which indicates to me that nothing is getting to expected location on the hardware.  Like I said above...I force the PINMUX registers to the same values that I see when I use the emulator.  To do this, I also use the ioremap command with the specific address of each PINMUX register and assign the values in a similar fashion as I showed above with the 0x66000000 memory location.  I was using this thread as an example to follow:  http://forums.freescale.com/t5/68K-ColdFire-reg-Microprocessors/Memory-mapped-I-O-from-Linux-device-driver/td-p/46977

Anyone have any other ideas?  I know there is a PINMUX utility that creates a header file that can be included in an application...but I don't see how this would be any more beneficial than forcing the specific memory locations to specified values.

Thanks for any help.

  • Which linux version are you using? You should be able to clone the existing nor or nand EMIF code for your purposes. Did you enable the EMIF clock?

  • Hi Norman,

    I am using PSP-SDK-03.21.00.04. (Linux 2.6.37).  Yes I enabled the EMIF clock. If I change the 0x66000000 address via CodeComposer Memory Browser...I get the trigger I am looking for.  I know in CodeComposer there is a fair amount registers being setup in the GEL file.  Based upon my experience, it looks like all I need to be concerned with are the following:

    0x6800001c (CE5CFG Register)  - We are currently setting this to a value of BFFFFFFD
    0x01c14138  (PINMUX 6) - Value = 00000081    (turns on the clock)
    0x01c1413c (PINMUX 7) - Value = 00111111
    0x01c14140 (PINMUX 8) - Value = 11111111
    0x01c14144 (PINMUX 9) - Value = 11111111
    0x01c14148 (PINMUX 10) - Value = 11111111
    0x01c1414c (PINMUX 11) - Value = 11111111
    0x01c14150 (PINMUX 12) - Value = 11111111

    I have forced these memory values in my C code, but I still don't get the trigger I expect when I change the 0x66000000 address.  I have proven that I can stop and start the EMIF clock from within my C code ... so using the ioremap appears to work (at least for the configuration registers).

    Can you elaborate a little on cloning the nand EMIF code for my purposes?

    Thanks!

  • I've never actually had to configure the EMIF yet...so take this with a grain of salt. I'll eventually have to connect to a CPLD. I am thinking that I'll try to clone the NOR flash code to setup the EMIF. The NOR flash code is all over the place amd I'll probably collect it all into one place in the board init file. A trace of the DA850/OMAP-L138/AM1808? code below. If you figure out a "simple" way to setup EMIF A or B, please post it. Seems nothing in Linux is simple or easy.

     

    ----------arch/arm/mach-davinci/board-da850.c----------
    // Look for _norflash_ structures. Lotta of EMIF timing stuff omitted
    // This structure has the memory and CS
    static struct resource da850_evm_norflash_resource[] = {
        {
            .start    = DA8XX_AEMIF_CS2_BASE,
            .end    = DA8XX_AEMIF_CS2_BASE + SZ_32M - 1,
            .flags    = IORESOURCE_MEM,
        },
        {
            .start    = DA8XX_AEMIF_CTL_BASE,
            .end    = DA8XX_AEMIF_CTL_BASE + SZ_32K - 1,
            .flags    = IORESOURCE_MEM,
        },
    };
    // This structure ties the driver with its config data by name
    static struct platform_device da850_evm_norflash_device = {
        .name        = "davinci-flash",
        .id        = 0,
        .dev        = {
            .platform_data  = &da850_evm_norflash_data,
        },
        .num_resources    = ARRAY_SIZE(da850_evm_norflash_resource),
        .resource    = da850_evm_norflash_resource,
    };
    // This structure puts NOR structures in the "constructors" list.
    // One of those Linux auto-magic things???.
    static struct platform_device *da850_evm_devices[] __initdata = {
    ...
        &da850_evm_norflash_device,
    };
    // This structure specifies which pins to MUX in.
    static const short da850_evm_nor_pins[] = {
        DA850_EMA_BA_1, DA850_EMA_CLK, DA850_EMA_WAIT_1, DA850_NEMA_CS_2,
    ...
        -1
    };
    ...
    static inline void da850_evm_setup_nor_nand(void)
    {
    ...
    //This function uses the pin list above to actually mod the PINMUX registers.
            ret = davinci_cfg_reg_list(da850_evm_nor_pins);
    ...
    }
    ----------drivers/mtd/maps/davinci-nor.c--------
    static int __init davinciflash_probe(struct platform_device *pdev)
    {
    ...
    // Get the virtual address for my physical address
        info->map.virt = ioremap(res1->start, resource_size(res1));
        info->ctlr = ioremap(res2->start, resource_size(res2));
    ...
    // Get the EMIF clock domain by name
        info->clk = clk_get(&pdev->dev, "aemif");
    ...
    // Enable it.
        ret = clk_enable(info->clk);
    ...
    }
    ...
    // Name of this driver specified here.
    static struct platform_driver davinciflash_driver = {
    ...
        .driver = {
            .name    = "davinci-flash",
        },
    ...
    };

  • Hi Norman,

    Thanks - I will be looking at the references you cited and see if I can pull out what I need.  I was hoping it was going to be more straight forward...but I guess that is just a pipe dream. Once I get something to work here...I will post back and perhaps I will be able to help your future efforts as well.

  • Hi Dan,

     

    Did you finally solve the issue ? I did a few tests on an AM1808 platform and at first sight I'm experiencing the same behaviour as you.

     

    Regards,

    Jerome

  • Hi Jerome,

    Sorry to keep this thread hanging - after a long struggle and getting a paid consultant involved, I had forgotten to come back and post the solution (at least what worked for us). 

    Here is the code we wound up using to initialize the FPGA:

    #define FPGA_ADDR   0x66000000
    #define FPGA_RANGE   0x80

    static struct davinci_aemif_timing da850_naii_timing = {
     .wsetup         = 1,
     .wstrobe        = 1,  
     .whold          = 1,
     .rsetup         = 1,  
     .rstrobe        = 1,
     .rhold          = 0,
     .ta             = 1,  
    };

    int nai_initFPGA(void)
    {
     int ret = 0;
     int result;
     struct clk *aemif_clk;

     // if already initialized...no work to be done
     if (nInitializedFPGA == 1)
      return 0;

     // request the FPGA memory region
     if (!request_mem_region(FPGA_ADDR,FPGA_RANGE, "naii_fpga")) {
      printk(KERN_INFO "Unable to request fpga registers\n");
      ret = -1;
      goto done;
     }

     // perform tx ioremap
     nai_FPGAMap = ioremap(FPGA_ADDR,FPGA_RANGE);
     if (!nai_FPGAMap) {
      printk(KERN_INFO "Unable to ioremap fpga\n");
      ret = -1;
      goto release_mem;
     }

     // setup the clock
     aemif_clk = clk_get(NULL, "aemif");
     if (IS_ERR(aemif_clk)) {
      printk(KERN_INFO "Failed to get clock for aemif\n");
      ret = -1;
      goto unmap_mem;
     }

     result = clk_enable(aemif_clk);
     if (result < 0) {
      printk(KERN_INFO "Unable to enable aemif clk\n");
      ret = -1;
      goto unmap_mem;
     }

     // setup timing for tx
     result = davinci_aemif_setup_timing(&da850_naii_timing,
         nai_FPGAMap,
         5);
     if (result < 0) {
      printk(KERN_INFO "Unable to setup aemif timing for fpga\n");
      ret = -1;
      goto unmap_mem;
     }

     goto done;

     //release fpga
    unmap_mem:
     iounmap(nai_FPGAMap);
    release_mem:
     release_mem_region(FPGA_ADDR,FPGA_RANGE);
    done:
     nInitializedFPGA = 1; 

     return ret;
    }
    EXPORT_SYMBOL(nai_initFPGA);

    Once initialized ... you should then be able to use the functions iowrite16(...) and ioread16(...) to write and read from the FPGA/FIFO.

    Hope it helps and you don't struggle for nearly as long as I did  -  Wish T.I. had better documentation on this!

  • Thanks a LOT Dan, for such a great followup.

     

    I'm 100% sure this thread will be useful to many people.

     

    Regards,

     

    Jerome

  • Thanks Dan. Much appreciated.

     

  • I will also add an important note:

     

    You need to setup the appropriate PINMUX settings using the davinci_cfg_reg() / davinci_cfg_reg_list() functions from mux.h, otherwise your AEMIF pins might be muxed to some other use.

  • Hi Jerome,
    I don't think that was an issue for me since I force the registers to my desired values in uboot params using the mw function...but it is good that you added the requirement to this thread.