/* * dspdl_dm644x.c - DM644X DSP loader driver * * Copyright (C) 2008 Lyrtech * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For ioremap() */ #include #include #include #define MODULE_NAME "dspdl_dm644x" #define MODULE_VERSION_STR "v1.1" /* Define this to enable verbose debug messages */ #define DSPDL_DM644X_DEBUG 1 /* Module parameters */ static unsigned int dspdl_dm644x_debug; module_param_named(debug, dspdl_dm644x_debug, int, 0644); static char *dspdl_dm644x_fw_name = NULL; module_param_named(fw_name, dspdl_dm644x_fw_name, charp, 0644); #ifdef DSPDL_DM644X_DEBUG #define INFOMSG(fmt, args...) \ do { \ printk(KERN_INFO "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) #define DBGMSG(fmt, args...) \ do { if (dspdl_dm644x_debug > 0) \ printk(KERN_DEBUG "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) #define DBGMSG_ENTER() \ DBGMSG("%s() enter", __func__); #define DBGMSG_LEAVE() \ DBGMSG("%s() leave", __func__); #else #define INFOMSG(fmt, args...) do {} while (0) #define DBGMSG(fmt, args...) do {} while (0) #define DBGMSG_ENTER() do {} while (0) #define DBGMSG_LEAVE() do {} while (0) #endif #define FAILMSG(fmt, args...) \ do { \ printk(KERN_ERR "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) /* * PSC register offsets * already defined in arch/arm/mach-davinci/psc.c */ #define DAVINCI_PWR_SLEEP_CNTRL_BASE 0x01C41000 #define EPCPR 0x070 #define PTCMD 0x120 #define PTSTAT 0x128 #define PDSTAT 0x200 #define PDCTL1 0x304 #define MDSTAT 0x800 #define MDCTL 0xA00 #define PSC_PID (DAVINCI_PWR_SLEEP_CNTRL_BASE + 0) #define PSC_EPCPR (DAVINCI_PWR_SLEEP_CNTRL_BASE + EPCPR) #define PSC_PTCMD (DAVINCI_PWR_SLEEP_CNTRL_BASE + PTCMD) #define PSC_PTSTAT (DAVINCI_PWR_SLEEP_CNTRL_BASE + PTSTAT) #define PSC_PDCTL1 (DAVINCI_PWR_SLEEP_CNTRL_BASE + PDCTL1) #define PSC_MDSTAT_DSP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDSTAT + \ (4 * DAVINCI_LPSC_GEM)) #define PSC_MDCTL_DSP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDCTL + \ (4 * DAVINCI_LPSC_GEM)) #define PSC_MDSTAT_IMCOP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDSTAT + \ (4 * DAVINCI_LPSC_IMCOP)) #define PSC_MDCTL_IMCOP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDCTL + \ (4 * DAVINCI_LPSC_IMCOP)) #define DSPBOOTADDR (DAVINCI_SYSTEM_MODULE_BASE + 8) /* * 0 = Assert local reset * 1 = De-assert local reset */ #define PSC_MDCTL_LRST_BIT (1 << 8) struct dspdl_dm644x_dev_t { char devname[32]; enum { DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED, DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D, DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P, DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2, DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR, DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED, } state; void *l1d_mmio; struct resource *l1d_mmio_res; void *l1p_mmio; struct resource *l1p_mmio_res; void *l2_mmio; struct resource *l2_mmio_res; void *ddr_mmio; struct resource *ddr_mmio_res; struct dspdl_device dspdl_dev; }; #define MAX_DSPDL_DM644X_DEV 5 static int dspdl_dm644x_dev_count; static void dsp_reset(int active) { u32 mdctl; void __iomem *psc_base = IO_ADDRESS(DAVINCI_PWR_SLEEP_CNTRL_BASE); mdctl = __raw_readl(psc_base + MDCTL + 4 * DAVINCI_LPSC_GEM); if (active) mdctl &= ~PSC_MDCTL_LRST_BIT; else mdctl |= PSC_MDCTL_LRST_BIT; __raw_writel(mdctl, psc_base + MDCTL + 4 * DAVINCI_LPSC_GEM); } static int dm644x_start(struct dspdl_device *dspdl_dev) { DBGMSG_ENTER(); /* Put DSP in reset */ dsp_reset(1); return 0; } static int dm644x_finish(struct dspdl_device *dspdl_dev, u32 entry_point) { void __iomem *dspbootaddr_reg = IO_ADDRESS(DSPBOOTADDR); DBGMSG_ENTER(); /* Verify if the DSP boot address first 10 bits are zero. */ if ((entry_point & 0x000003FF) != 0x000) { FAILMSG("Application entry point not aligned on a 10 bits boundary."); return -EFAULT; } __raw_writel(entry_point, dspbootaddr_reg); DBGMSG(" Taking DSP out of reset"); dsp_reset(0); return 0; } static int dm644x_write_section(struct dspdl_device *dspdl_dev, u32 start_addr, u8 *data, ssize_t size) { u32 offset; struct dspdl_dm644x_dev_t *dspdl_dm644x_dev = (struct dspdl_dm644x_dev_t *) dspdl_dev->devdata; DBGMSG_ENTER(); if ( (dspdl_dm644x_dev->l1d_mmio_res) && (start_addr >= dspdl_dm644x_dev->l1d_mmio_res->start) && (start_addr <= dspdl_dm644x_dev->l1d_mmio_res->end)) { if ((start_addr+size) > dspdl_dm644x_dev->l1d_mmio_res->end) { FAILMSG("Section exceeds L1D memory region."); return -EFAULT; } offset = start_addr - dspdl_dm644x_dev->l1d_mmio_res->start; memcpy(dspdl_dm644x_dev->l1d_mmio + offset, data, size); if (memcmp(dspdl_dm644x_dev->l1d_mmio + offset, data, size)) { FAILMSG("Failed to write into L1D memory region."); return -EFAULT; } } else if ( (dspdl_dm644x_dev->l1p_mmio_res) && (start_addr >= dspdl_dm644x_dev->l1p_mmio_res->start) && (start_addr <= dspdl_dm644x_dev->l1p_mmio_res->end)) { if ((start_addr+size) > dspdl_dm644x_dev->l1p_mmio_res->end) { FAILMSG("Section exceeds L1P memory region."); return -EFAULT; } offset = start_addr - dspdl_dm644x_dev->l1p_mmio_res->start; memcpy(dspdl_dm644x_dev->l1p_mmio + offset, data, size); if (memcmp(dspdl_dm644x_dev->l1p_mmio + offset, data, size)) { FAILMSG("Failed to write into L1P memory region."); return -EFAULT; } } else if ( (dspdl_dm644x_dev->l2_mmio_res) && (start_addr >= dspdl_dm644x_dev->l2_mmio_res->start) && (start_addr <= dspdl_dm644x_dev->l2_mmio_res->end)) { if ((start_addr+size) > dspdl_dm644x_dev->l2_mmio_res->end) { FAILMSG("Section exceeds L2 memory region."); return -EFAULT; } offset = start_addr - dspdl_dm644x_dev->l2_mmio_res->start; memcpy(dspdl_dm644x_dev->l2_mmio + offset, data, size); if (memcmp(dspdl_dm644x_dev->l2_mmio + offset, data, size)) { FAILMSG("Failed to write into L2 memory region."); return -EFAULT; } } else if ( (dspdl_dm644x_dev->ddr_mmio_res) && (start_addr >= dspdl_dm644x_dev->ddr_mmio_res->start) && (start_addr <= dspdl_dm644x_dev->ddr_mmio_res->end)) { if ((start_addr+size) > dspdl_dm644x_dev->ddr_mmio_res->end) { FAILMSG("Section exceeds DDR memory region."); return -EFAULT; } offset = start_addr - dspdl_dm644x_dev->ddr_mmio_res->start; memcpy(dspdl_dm644x_dev->ddr_mmio + offset, data, size); if (memcmp(dspdl_dm644x_dev->ddr_mmio + offset, data, size)) { FAILMSG("Failed to write into DDR memory region."); return -EFAULT; } } else { FAILMSG("Invalid destination address."); return -EFAULT; } return 0; } static void dspdl_dm644x_cleanup(struct dspdl_dm644x_dev_t *dev) { DBGMSG_ENTER(); if (!dev) return; switch (dev->state) { case DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED: dspdl_unregister_device(&dev->dspdl_dev); case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR: iounmap(dev->ddr_mmio); case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2: iounmap(dev->l2_mmio); case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P: iounmap(dev->l1p_mmio); case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D: iounmap(dev->l1d_mmio); case DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED: kfree(dev); break; } } static int __devinit dspdl_dm644x_probe(struct platform_device *pdev) { int len; int res; struct dspdl_dm644x_dev_t *dev = NULL; DBGMSG_ENTER(); if (dspdl_dm644x_dev_count == MAX_DSPDL_DM644X_DEV) { FAILMSG("Maximum number of devices reached (%d)", dspdl_dm644x_dev_count); res = -ENODEV; goto error; } DBGMSG(" device %d", dspdl_dm644x_dev_count); dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { FAILMSG("Failed to allocate device structure"); res = -ENOMEM; goto error; } dev->state = DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED; //pdev->dev.driver_data = dev; /* Private driver data */ /* Assign virtual addresses to L1D memory regions. */ dev->l1d_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "l1d"); if (dev->l1d_mmio_res) { len = dev->l1d_mmio_res->end - dev->l1d_mmio_res->start; dev->l1d_mmio = ioremap(dev->l1d_mmio_res->start, len); if (!dev->l1d_mmio) { FAILMSG("Can't remap l1d_mmio register"); res = -ENXIO; goto error; } } dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D; /* Assign virtual addresses to L1P memory regions. */ dev->l1p_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "l1p"); if (dev->l1p_mmio_res) { len = dev->l1p_mmio_res->end - dev->l1p_mmio_res->start; dev->l1p_mmio = ioremap(dev->l1p_mmio_res->start, len); if (!dev->l1p_mmio) { FAILMSG("Can't remap l1p_mmio register"); res = -ENXIO; goto error; } } dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P; /* Assign virtual addresses to L2 memory regions. */ dev->l2_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "l2"); if (dev->l2_mmio_res) { len = dev->l2_mmio_res->end - dev->l2_mmio_res->start; dev->l2_mmio = ioremap(dev->l2_mmio_res->start, len); if (!dev->l2_mmio) { FAILMSG("Can't remap l2_mmio register"); res = -ENXIO; goto error; } } dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2; /* Assign virtual addresses to DDR memory regions. */ dev->ddr_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ddr"); if (dev->ddr_mmio_res) { len = dev->ddr_mmio_res->end - dev->ddr_mmio_res->start; dev->ddr_mmio = ioremap(dev->ddr_mmio_res->start, len); if (!dev->ddr_mmio) { FAILMSG("Can't remap ddr_mmio register"); res = -ENXIO; goto error; } } dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR; if (dspdl_dm644x_fw_name) dev->dspdl_dev.fw_name = dspdl_dm644x_fw_name; dev->dspdl_dev.cb_start = dm644x_start; dev->dspdl_dev.cb_finish = dm644x_finish; dev->dspdl_dev.cb_write_section = dm644x_write_section; sprintf(dev->devname, "%s_%d", MODULE_NAME, dspdl_dm644x_dev_count); DBGMSG(" NAME = %s", dev->devname); dev->dspdl_dev.name = dev->devname; dev->dspdl_dev.devdata = dev; /* For our callbacks */ res = dspdl_register_device(&dev->dspdl_dev); if (res < 0) { FAILMSG("Error registering dspdl_dm644x device"); goto error; } dev->state = DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED; dspdl_dm644x_dev_count++; return 0; error: dspdl_dm644x_cleanup(dev); return res; } static int __devexit dspdl_dm644x_remove(struct platform_device *pdev) { struct dspdl_dm644x_dev_t *dev = platform_get_drvdata(pdev); DBGMSG_ENTER(); dspdl_dm644x_cleanup(dev); return 0; } static struct dspdl_driver dspdl_dm644x_driver = { .version = MODULE_VERSION_STR, .module = THIS_MODULE, .driver = { .name = "dspdl_dm644x", }, }; static struct platform_driver dspdl_platform_driver = { .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, }, .remove = dspdl_dm644x_remove, }; static int __init dspdl_dm644x_init(void) { int res; DBGMSG_ENTER(); INFOMSG("DM644X DSP firmware loader %s", MODULE_VERSION_STR); /* Register with the driver core. */ res = dspdl_register_driver(&dspdl_dm644x_driver); if (res) { FAILMSG("Can't register dspdl DM644X driver"); return res; } /* The probe function will be called for each platform device declared * in board setup code. */ res = platform_driver_probe(&dspdl_platform_driver, dspdl_dm644x_probe); if (res) { FAILMSG("platform_driver_probe() failed"); return res; } return 0; } module_init(dspdl_dm644x_init); static void __exit dspdl_dm644x_exit(void) { DBGMSG_ENTER(); platform_driver_unregister(&dspdl_platform_driver); dspdl_unregister_driver(&dspdl_dm644x_driver); } module_exit(dspdl_dm644x_exit); MODULE_AUTHOR("Hugo Villeneuve "); MODULE_DESCRIPTION("DM644X DSP loader driver"); MODULE_LICENSE("GPL");