| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define DEBUG |
| |
| #define pr_fmt(fmt) "hifi4dsp: " fmt |
| |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/sysfs.h> |
| #include <linux/string.h> |
| #include <linux/io.h> |
| #include <linux/fs.h> |
| #include <linux/mm.h> |
| #include <linux/interrupt.h> |
| #include <linux/mutex.h> |
| #include <linux/device.h> |
| #include <linux/timer.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/reset.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/uaccess.h> |
| #include <linux/miscdevice.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/cma.h> |
| #include <linux/dma-contiguous.h> |
| #include <linux/vmalloc.h> |
| #include <linux/clk.h> |
| #include <asm/cacheflush.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/pm_domain.h> |
| #include <linux/amlogic/scpi_protocol.h> |
| #include <linux/compat.h> |
| #include "hifi4dsp_api.h" |
| #include "hifi4dsp_priv.h" |
| #include "hifi4dsp_firmware.h" |
| #include "hifi4dsp_dsp.h" |
| |
| #include "dsp_top.h" |
| |
| struct reg_iomem_t g_regbases; |
| unsigned int bootlocation; |
| static unsigned int boot_sram_addr, boot_sram_size; |
| static struct mutex hifi4dsp_flock; |
| |
| static struct reserved_mem hifi4_rmem; /*dsp firmware memory*/ |
| static struct reserved_mem hifi_shmem; /*dsp share memory*/ |
| |
| struct hifi4dsp_priv *hifi4dsp_p[HIFI4DSP_MAX_CNT]; |
| struct delayed_work dsp_status_work; |
| struct delayed_work dsp_logbuff_work; |
| struct workqueue_struct *dsp_status_wq; |
| struct workqueue_struct *dsp_logbuff_wq; |
| static unsigned long dsp_online; |
| static unsigned int dsp_monitor_period_ms = 2000; |
| static unsigned int dsp_logbuff_polling_period_ms = 2000; |
| #define SUSPEND_CLK_FREQ 24000000 |
| |
| #define MASK_BF(x, mask, shift) ((((x) & (mask)) << (shift))) |
| |
| static int hifi4dsp_driver_load_fw(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_load_2fw(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_reset(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_dsp_start(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_dsp_stop(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_dsp_sleep(struct hifi4dsp_dsp *dsp); |
| static int hifi4dsp_driver_dsp_wake(struct hifi4dsp_dsp *dsp); |
| |
| static int hifi4dsp_miscdev_open(struct inode *inode, struct file *fp) |
| { |
| int minor = iminor(inode); |
| int major = imajor(inode); |
| struct hifi4dsp_priv *priv; |
| struct miscdevice *c; |
| struct hifi4dsp_miscdev_t *pmscdev_t; |
| |
| c = fp->private_data; |
| pr_debug("%s,%s,major=%d,minor=%d\n", __func__, |
| c->name, |
| major, |
| minor); |
| |
| if (strcmp(c->name, "hifi4dsp0") == 0) |
| priv = hifi4dsp_p[0]; |
| else if (strcmp(c->name, "hifi4dsp1") == 0) |
| priv = hifi4dsp_p[1]; |
| else |
| priv = NULL; |
| |
| pmscdev_t = container_of(c, struct hifi4dsp_miscdev_t, dsp_miscdev); |
| if (!pmscdev_t) { |
| pr_err("hifi4dsp_miscdev_t == NULL\n"); |
| return -1; |
| } |
| pmscdev_t->priv = priv; |
| if (!pmscdev_t->priv) { |
| pr_err("hifi4dsp_miscdev_t pointer *pmscdev_t==NULL\n"); |
| return -1; |
| } |
| |
| fp->private_data = priv; |
| |
| pr_debug("%s,%s,major=%d,minor=%d\n", __func__, |
| priv->dev->kobj.name, |
| major, |
| minor); |
| return 0; |
| } |
| |
| static int hifi4dsp_miscdev_release(struct inode *inode, struct file *fp) |
| { |
| return 0; |
| } |
| |
| struct hifi4dsp_addr *hifi4dsp_get_share_memory(void) |
| { |
| if (!hifi4dsp_p[0] || !hifi4dsp_p[0]->dsp) { |
| pr_err("%s failed.\n", __func__); |
| return NULL; |
| } |
| return &hifi4dsp_p[0]->dsp->addr; |
| } |
| EXPORT_SYMBOL(hifi4dsp_get_share_memory); |
| |
| struct hifi4dsp_firmware *hifi4dsp_get_firmware(int dspid) |
| { |
| if (!hifi4dsp_p[dspid]) |
| return NULL; |
| |
| return hifi4dsp_p[dspid]->dsp->dsp_fw; |
| } |
| |
| void hifi4dsp_shm_clean(int dspid, unsigned int paddr, unsigned int size) |
| { |
| if (!hifi4dsp_p[dspid]) |
| return; |
| dma_sync_single_for_device(hifi4dsp_p[dspid]->dev, paddr, size, DMA_TO_DEVICE); |
| } |
| |
| void hifi4dsp_shm_invalidate(int dspid, unsigned int paddr, unsigned int size) |
| { |
| if (!hifi4dsp_p[dspid]) |
| return; |
| dma_sync_single_for_device(hifi4dsp_p[dspid]->dev, paddr, size, DMA_FROM_DEVICE); |
| } |
| |
| static long hifi4dsp_miscdev_unlocked_ioctl(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret = 0; |
| struct hifi4dsp_priv *priv; |
| struct hifi4dsp_dsp *dsp; |
| struct hifi4dsp_info_t *usrinfo; |
| struct hifi4_shm_info_t shminfo; |
| void __user *argp; |
| |
| pr_debug("%s\n", __func__); |
| if (!fp->private_data) { |
| pr_err("%s error:fp->private_data is null", __func__); |
| return -1; |
| } |
| |
| mutex_lock(&hifi4dsp_flock); |
| argp = (void __user *)arg; |
| priv = (struct hifi4dsp_priv *)fp->private_data; |
| if (!priv) { |
| pr_err("%s error: hifi4dsp_priv is null", __func__); |
| ret = -1; |
| goto err; |
| } |
| dsp = priv->dsp; |
| if (!dsp) { |
| pr_err("%s hifi4dsp_dsp is null:\n", __func__); |
| ret = -1; |
| goto err; |
| } |
| if (!priv->dsp->dsp_fw) { |
| pr_err("%s hifi4dsp_firmware is null:\n", __func__); |
| ret = -1; |
| goto err; |
| } |
| if (!priv->dsp->fw) { |
| pr_err("%s firmware is null:\n", __func__); |
| ret = -1; |
| goto err; |
| } |
| pr_debug("%s %s\n", __func__, priv->dev->kobj.name); |
| usrinfo = kmalloc(sizeof(*usrinfo), GFP_KERNEL); |
| if (!usrinfo) { |
| ret = -1; |
| goto err; |
| } |
| |
| switch (cmd) { |
| case HIFI4DSP_TEST: |
| pr_debug("%s HIFI4DSP_TEST\n", __func__); |
| break; |
| case HIFI4DSP_LOAD: |
| pr_debug("%s HIFI4DSP_LOAD\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| pr_debug("\ninfo->fw1_name : %s\n", usrinfo->fw1_name); |
| pr_debug("\ninfo->fw2_name : %s\n", usrinfo->fw2_name); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_load_fw(priv->dsp); |
| break; |
| case HIFI4DSP_2LOAD: |
| pr_debug("%s HIFI4DSP_2LOAD\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_load_2fw(priv->dsp); |
| break; |
| case HIFI4DSP_RESET: |
| pr_debug("%s HIFI4DSP_RESET\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_reset(priv->dsp); |
| break; |
| case HIFI4DSP_START: |
| pr_debug("%s HIFI4DSP_START\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_dsp_start(priv->dsp); |
| break; |
| case HIFI4DSP_STOP: |
| pr_debug("%s HIFI4DSP_STOP\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_dsp_stop(priv->dsp); |
| break; |
| case HIFI4DSP_SLEEP: |
| pr_debug("%s HIFI4DSP_SLEEP\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_dsp_sleep(priv->dsp); |
| break; |
| case HIFI4DSP_WAKE: |
| pr_debug("%s HIFI4DSP_WAKE\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| priv->dsp->info = usrinfo; |
| hifi4dsp_driver_dsp_wake(priv->dsp); |
| break; |
| case HIFI4DSP_GET_INFO: |
| pr_debug("%s HIFI4DSP_GET_INFO\n", __func__); |
| ret = copy_from_user(usrinfo, argp, |
| sizeof(struct hifi4dsp_info_t)); |
| pr_debug("%s HIFI4DSP_GET_INFO %s\n", __func__, |
| usrinfo->fw_name); |
| strcpy(usrinfo->fw_name, "1234abcdef"); |
| usrinfo->phy_addr = priv->pdata->fw_paddr; |
| usrinfo->size = priv->pdata->fw_max_size; |
| ret = copy_to_user(argp, usrinfo, |
| sizeof(struct hifi4dsp_info_t)); |
| pr_debug("%s HIFI4DSP_GET_INFO %s\n", __func__, |
| usrinfo->fw_name); |
| break; |
| case HIFI4DSP_SHM_CLEAN: |
| ret = copy_from_user(&shminfo, argp, sizeof(shminfo)); |
| pr_debug("%s clean cache, addr:%u, size:%u\n", |
| __func__, shminfo.addr, shminfo.size); |
| dma_sync_single_for_device |
| (priv->dev, |
| shminfo.addr, |
| shminfo.size, |
| DMA_TO_DEVICE); |
| break; |
| case HIFI4DSP_SHM_INV: |
| ret = copy_from_user(&shminfo, argp, sizeof(shminfo)); |
| pr_debug("%s invalidate cache, addr:%u, size:%u\n", |
| __func__, shminfo.addr, shminfo.size); |
| dma_sync_single_for_device(priv->dev, |
| shminfo.addr, |
| shminfo.size, |
| DMA_FROM_DEVICE); |
| break; |
| default: |
| pr_err("%s ioctl CMD error\n", __func__); |
| break; |
| } |
| kfree(usrinfo); |
| priv->dsp->info = NULL; |
| err: |
| mutex_unlock(&hifi4dsp_flock); |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long hifi4dsp_miscdev_compat_ioctl(struct file *filp, |
| unsigned int cmd, unsigned long args) |
| { |
| long ret; |
| |
| args = (unsigned long)compat_ptr(args); |
| ret = hifi4dsp_miscdev_unlocked_ioctl(filp, cmd, args); |
| |
| return ret; |
| } |
| #endif |
| |
| static int hifi4dsp_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) |
| { |
| int ret = 0; |
| unsigned long phys_page_addr = 0; |
| unsigned long size = 0; |
| struct hifi4dsp_priv *priv = NULL; |
| |
| if (!vma) { |
| pr_err("input error: vma is NULL\n"); |
| return -1; |
| } |
| |
| pr_debug("%s\n", __func__); |
| if (!fp->private_data) { |
| pr_err("%s error:fp->private_data is null", __func__); |
| return -1; |
| } |
| |
| priv = (struct hifi4dsp_priv *)fp->private_data; |
| |
| phys_page_addr = (u64)priv->pdata->fw_paddr >> PAGE_SHIFT; |
| size = ((unsigned long)vma->vm_end - (unsigned long)vma->vm_start); |
| pr_err("vma=0x%pK.\n", vma); |
| pr_err("size=%ld, vma->vm_start=%ld, end=%ld.\n", |
| ((unsigned long)vma->vm_end - (unsigned long)vma->vm_start), |
| (unsigned long)vma->vm_start, (unsigned long)vma->vm_end); |
| pr_err("phys_page_addr=%ld.\n", (unsigned long)phys_page_addr); |
| |
| vma->vm_page_prot = PAGE_SHARED; |
| vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| |
| if (size > priv->pdata->fw_max_size) |
| size = priv->pdata->fw_max_size; |
| |
| ret = remap_pfn_range(vma, |
| vma->vm_start, |
| phys_page_addr, size, vma->vm_page_prot); |
| if (ret != 0) { |
| pr_err("remap_pfn_range ret=%d\n", ret); |
| return -1; |
| } |
| |
| return ret; |
| } |
| |
| static int hifi4dsp_driver_load_fw(struct hifi4dsp_dsp *dsp) |
| { |
| int err = 0; |
| struct hifi4dsp_firmware *new_dsp_fw; |
| struct hifi4dsp_info_t *info = dsp->info; |
| |
| if (strlen(info->fw_name) == 0) |
| return -1; |
| |
| pr_debug("hifi4dsp_info_t, name=%s, paddr=0x%llx\n", |
| info->fw_name, |
| (unsigned long long)info->phy_addr); |
| |
| new_dsp_fw = hifi4dsp_fw_register(dsp, info->fw_name); |
| if (!new_dsp_fw) { |
| pr_err("register firmware:%s error\n", info->fw_name); |
| return -1; |
| } |
| dsp->dsp_fw = new_dsp_fw; /*set newest fw as def fw of dsp*/ |
| strcpy(new_dsp_fw->name, info->fw_name); |
| if (info->phy_addr != 0) { /*to be improved*/ |
| //info->phy_addr may !=0, but illegal |
| new_dsp_fw->paddr = info->phy_addr; |
| /*TODO*/ |
| /*new_dsp_fw->buf = phys_to_virt(new_dsp_fw->paddr);*/ |
| new_dsp_fw->buf = dsp->pdata->fw_buf; |
| } else { |
| new_dsp_fw->paddr = dsp->pdata->fw_paddr; |
| new_dsp_fw->buf = dsp->pdata->fw_buf; |
| } |
| pr_debug("new hifi4dsp_firmware, name=%s, paddr=0x%llx, buf=0x%p\n", |
| new_dsp_fw->name, |
| (unsigned long long)new_dsp_fw->paddr, |
| new_dsp_fw->buf); |
| |
| hifi4dsp_fw_load(new_dsp_fw); |
| |
| return err; |
| } |
| |
| static int hifi4dsp_driver_load_2fw(struct hifi4dsp_dsp *dsp) |
| { |
| int err = 0; |
| struct hifi4dsp_firmware *new_dsp_sram_fw; |
| struct hifi4dsp_firmware *new_dsp_ddr_fw; |
| struct hifi4dsp_info_t *info = dsp->info; |
| |
| if (strlen(info->fw1_name) == 0) |
| return -1; |
| /*load dspboot_sdram.bin to ddr*/ |
| pr_debug("info->fw1_name : %s\n", info->fw1_name); |
| new_dsp_ddr_fw = hifi4dsp_fw_register(dsp, info->fw1_name); |
| if (!new_dsp_ddr_fw) { |
| pr_err("register firmware:%s error\n", info->fw1_name); |
| return -1; |
| } |
| dsp->dsp_fw = new_dsp_ddr_fw; /*set newest fw as def fw of dsp*/ |
| strcpy(new_dsp_ddr_fw->name, info->fw1_name); |
| new_dsp_ddr_fw->paddr = dsp->pdata->fw_paddr; |
| new_dsp_ddr_fw->buf = dsp->pdata->fw_buf; |
| pr_debug("new_dsp_ddr_fw, name=%s, paddr=0x%llx, virtual addr:0x%lx\n", |
| new_dsp_ddr_fw->name, |
| (unsigned long long)new_dsp_ddr_fw->paddr, |
| (unsigned long)new_dsp_ddr_fw->buf); |
| hifi4dsp_fw_load(new_dsp_ddr_fw); |
| |
| /*load dspboot_sram.bin to sram*/ |
| if (strlen(info->fw2_name) == 0) |
| return -1; |
| pr_debug("info->fw2_name : %s\n", info->fw2_name); |
| new_dsp_sram_fw = hifi4dsp_fw_register(dsp, info->fw2_name); |
| if (!new_dsp_sram_fw) { |
| pr_err("register firmware:%s error\n", info->fw2_name); |
| return -1; |
| } |
| dsp->dsp_fw = new_dsp_sram_fw; |
| strcpy(new_dsp_sram_fw->name, info->fw2_name); |
| new_dsp_sram_fw->paddr = boot_sram_addr; |
| new_dsp_sram_fw->buf = g_regbases.sram_base; |
| hifi4dsp_fw_sram_load(new_dsp_sram_fw); |
| |
| return err; |
| } |
| |
| static int hifi4dsp_driver_reset(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| pr_debug("%s\n", __func__); |
| if (!dsp->info) |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| |
| dsp->info = NULL; |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_boot(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| pr_debug("%s\n", __func__); |
| |
| dsp->info = NULL; |
| return 0; |
| } |
| |
| enum dsp_online_status { |
| DSP_NONE, |
| DSPA_ONLINE = 1, |
| DSPB_ONLINE, |
| DSPAB_ONLINE |
| }; |
| |
| enum dsp_health { |
| DSP_GOOD, |
| DSPA_HANG = 1, |
| DSPB_HANG, |
| DSPAB_HANG |
| }; |
| |
| enum dsp_index { |
| DSPA = 0, |
| DSPB |
| }; |
| static enum dsp_health get_dsp_health_status(unsigned long online) |
| { |
| enum dsp_health ret = DSP_GOOD; |
| static u32 last_cnt[HIFI4DSP_MAX_CNT] = {0}; |
| u32 this_cnt[HIFI4DSP_MAX_CNT]; |
| |
| switch (online & 0x03) { |
| case DSP_NONE: |
| break; |
| case DSPA_ONLINE: |
| this_cnt[DSPA] = readl(hifi4dsp_p[DSPA]->dsp->status_reg); |
| pr_debug("[%s]dspa[%u %u]\n", __func__, last_cnt[DSPA], this_cnt[DSPA]); |
| if (this_cnt[DSPA] == last_cnt[DSPA]) |
| ret = DSPA_HANG; |
| last_cnt[DSPA] = this_cnt[DSPA]; |
| break; |
| case DSPB_ONLINE: |
| this_cnt[DSPB] = readl(hifi4dsp_p[DSPB]->dsp->status_reg); |
| pr_debug("[%s]dspb[%u %u]\n", __func__, last_cnt[DSPB], this_cnt[DSPB]); |
| if (this_cnt[DSPB] == last_cnt[DSPB]) |
| ret = DSPB_HANG; |
| last_cnt[DSPB] = this_cnt[DSPB]; |
| break; |
| case DSPAB_ONLINE: |
| this_cnt[DSPA] = readl(hifi4dsp_p[DSPA]->dsp->status_reg); |
| this_cnt[DSPB] = readl(hifi4dsp_p[DSPB]->dsp->status_reg); |
| pr_debug("[%s]dspa[%u %u]dspb[%u %u]\n", __func__, |
| last_cnt[DSPA], this_cnt[DSPA], last_cnt[DSPB], this_cnt[DSPB]); |
| if (this_cnt[DSPA] == last_cnt[DSPA] && this_cnt[DSPB] == last_cnt[DSPB]) |
| ret = DSPAB_HANG; |
| else if (this_cnt[DSPA] == last_cnt[DSPA]) |
| ret = DSPA_HANG; |
| else if (this_cnt[DSPB] == last_cnt[DSPB]) |
| ret = DSPB_HANG; |
| last_cnt[DSPA] = this_cnt[DSPA]; |
| last_cnt[DSPB] = this_cnt[DSPB]; |
| break; |
| } |
| return ret; |
| } |
| |
| static void dsp_health_monitor(struct work_struct *work) |
| { |
| char data[20], *envp[] = { data, NULL }; |
| |
| if (dsp_online == 0) |
| return; |
| |
| switch (get_dsp_health_status(dsp_online)) { |
| case DSP_GOOD: |
| hifi4dsp_p[DSPA]->dsp->dsphang = 0; |
| hifi4dsp_p[DSPB]->dsp->dsphang = 0; |
| break; |
| case DSPA_HANG: |
| snprintf(data, sizeof(data), "ACTION=DSP_WTD_A"); |
| hifi4dsp_p[DSPA]->dsp->dsphang = 1; |
| hifi4dsp_p[DSPB]->dsp->dsphang = 0; |
| kobject_uevent_env(&hifi4dsp_p[DSPA]->dsp->dev->kobj, KOBJ_CHANGE, envp); |
| pr_debug("[%s][DSPA_HANG]\n", __func__); |
| break; |
| case DSPB_HANG: |
| snprintf(data, sizeof(data), "ACTION=DSP_WTD_B"); |
| hifi4dsp_p[DSPA]->dsp->dsphang = 0; |
| hifi4dsp_p[DSPB]->dsp->dsphang = 1; |
| kobject_uevent_env(&hifi4dsp_p[DSPB]->dsp->dev->kobj, KOBJ_CHANGE, envp); |
| pr_debug("[%s][DSPB_HANG]\n", __func__); |
| break; |
| case DSPAB_HANG: |
| snprintf(data, sizeof(data), "ACTION=DSP_WTD_WHOLE"); |
| hifi4dsp_p[DSPA]->dsp->dsphang = 1; |
| hifi4dsp_p[DSPB]->dsp->dsphang = 1; |
| kobject_uevent_env(&hifi4dsp_p[DSPA]->dsp->dev->kobj, KOBJ_CHANGE, envp); |
| pr_debug("[%s][DSPAB_HANG]\n", __func__); |
| break; |
| } |
| |
| queue_delayed_work(dsp_status_wq, &dsp_status_work, |
| msecs_to_jiffies(dsp_monitor_period_ms)); |
| } |
| |
| static unsigned int show_logbuff_log(struct dsp_ring_buffer *rb, int dspid, |
| unsigned int len) |
| { |
| unsigned char *buffer; |
| unsigned int count = 0; |
| |
| if (!rb || !len) |
| return 0; |
| |
| buffer = kzalloc(len + 1, GFP_KERNEL); |
| if (!buffer) |
| return 0; |
| |
| while (len--) { |
| buffer[count++] = rb->data[rb->head++]; |
| rb->head %= rb->size; |
| } |
| pr_info("[%s]%s", dspid ? "DSPBB" : "DSPAA", buffer); |
| kfree(buffer); |
| |
| return count; |
| } |
| |
| static unsigned int get_logbuff_loglen(struct dsp_ring_buffer *rb) |
| { |
| if (!rb) |
| return 0; |
| if (rb->tail < rb->head) |
| return rb->tail + rb->size - rb->head; |
| else |
| return rb->tail - rb->head; |
| } |
| |
| static void dsp_logbuff_polling(struct work_struct *work) |
| { |
| struct dsp_ring_buffer *logbuffa = |
| (dsp_online & 0x1) ? hifi4dsp_p[DSPA]->dsp->logbuff : NULL; |
| struct dsp_ring_buffer *logbuffb = |
| (dsp_online & 0x2) ? hifi4dsp_p[DSPB]->dsp->logbuff : NULL; |
| unsigned int len_a, len_b; |
| |
| if (logbuffa) |
| pr_debug("[%s %d][0x%x 0x%x 0x%x %u %u]\n", __func__, __LINE__, |
| logbuffa->magic, logbuffa->basepaddr, logbuffa->size, |
| logbuffa->head, logbuffa->tail); |
| |
| len_a = get_logbuff_loglen(logbuffa); |
| len_b = get_logbuff_loglen(logbuffb); |
| if (len_a) |
| show_logbuff_log(logbuffa, DSPA, len_a); |
| if (len_b) |
| show_logbuff_log(logbuffb, DSPB, len_b); |
| |
| queue_delayed_work(dsp_logbuff_wq, &dsp_logbuff_work, |
| msecs_to_jiffies(dsp_logbuff_polling_period_ms)); |
| } |
| |
| static void init_and_start_dsp_log_polling(void) |
| { |
| if (!dsp_logbuff_wq) { |
| dsp_logbuff_wq = create_freezable_workqueue("dsplogbuff_wq"); |
| if (!dsp_logbuff_wq) |
| pr_err("create dsplogbuff_wq failed.\n"); |
| |
| INIT_DEFERRABLE_WORK(&dsp_logbuff_work, dsp_logbuff_polling); |
| queue_delayed_work(dsp_logbuff_wq, &dsp_logbuff_work, |
| msecs_to_jiffies(dsp_logbuff_polling_period_ms)); |
| } |
| } |
| |
| static int hifi4dsp_driver_dsp_start(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| pr_debug("%s\n", __func__); |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| pr_debug("dsp_id: %d\n", dsp->id); |
| pr_debug("dsp_freqence: %d Hz\n", dsp->freq); |
| pr_debug("dsp_start_addr: 0x%llx\n", |
| (unsigned long long)dsp->dsp_fw->paddr); |
| |
| if (!dsp->dsp_clk) { |
| pr_err("dsp_clk=NULL\n"); |
| return -EINVAL; |
| } |
| |
| clk_set_rate(dsp->dsp_clk, dsp->freq); |
| clk_prepare_enable(dsp->dsp_clk); |
| |
| if (bootlocation == 1) |
| soc_dsp_bootup(dsp->id, dsp->dsp_fw->paddr, dsp->freq); |
| else if (bootlocation == 2) |
| soc_dsp_bootup(dsp->id, boot_sram_addr, dsp->freq); |
| |
| dsp->info = NULL; |
| dsp->dspstarted = 1; |
| set_bit(dsp->id, &dsp_online); |
| pr_warn("[%s]dsp_online=0x%lx\n", __func__, dsp_online); |
| if (hifi4dsp_p[dsp->id]->dsp->status_reg && !dsp_status_wq) { |
| dsp_status_wq = create_freezable_workqueue("dspstatus_wq"); |
| if (!dsp_status_wq) { |
| pr_err("create dspstatus_wq failed.\n"); |
| return -EINVAL; |
| } |
| INIT_DEFERRABLE_WORK(&dsp_status_work, dsp_health_monitor); |
| queue_delayed_work(dsp_status_wq, &dsp_status_work, |
| msecs_to_jiffies(dsp_monitor_period_ms)); |
| } |
| if (dsp->logbuff) { |
| msleep(100); |
| scpi_send_data(dsp->logbuff, sizeof(struct dsp_ring_buffer), |
| dsp->id ? SCPI_DSPB : SCPI_DSPA, |
| SCPI_CMD_HIFI5_SYSLOG_START, NULL, 0); |
| init_and_start_dsp_log_polling(); |
| } |
| |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_clk_off(struct hifi4dsp_dsp *dsp) |
| { |
| if (!dsp->dsp_clk) { |
| pr_err("dsp_clk=NULL\n"); |
| return -EINVAL; |
| } |
| clk_disable_unprepare(dsp->dsp_clk); |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_stop(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| char message[30]; |
| |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| pr_debug("%s\n", __func__); |
| strcpy(message, "SCPI_CMD_HIFI4STOP"); |
| |
| if (dsp->dspstarted == 1) { |
| if (hifi4dsp_p[dsp->id]->dsp->status_reg && !dsp->dsphang) { |
| scpi_send_data(message, sizeof(message), dsp->id ? SCPI_DSPB : SCPI_DSPA, |
| SCPI_CMD_HIFI4STOP, NULL, 0); |
| msleep(50); |
| } |
| clear_bit(dsp->id, &dsp_online); |
| soc_dsp_poweroff(info->id); |
| hifi4dsp_driver_dsp_clk_off(dsp); |
| dsp->dspstarted = 0; |
| dsp->info = NULL; |
| pr_warn("[%s]dsp_online=0x%lx\n", __func__, dsp_online); |
| if (!dsp_online && dsp_status_wq) { |
| cancel_delayed_work_sync(&dsp_status_work); |
| flush_workqueue(dsp_status_wq); |
| destroy_workqueue(dsp_status_wq); |
| memset(&dsp_status_work, 0, sizeof(dsp_status_work)); |
| dsp_status_wq = NULL; |
| } |
| if (dsp_logbuff_wq && |
| (hifi4dsp_p[dsp->id ? DSPA : DSPB]->dsp->dspstarted == 0 || |
| !(hifi4dsp_p[dsp->id ? DSPA : DSPB]->dsp->logbuff))) { |
| cancel_delayed_work_sync(&dsp_logbuff_work); |
| flush_workqueue(dsp_logbuff_wq); |
| destroy_workqueue(dsp_logbuff_wq); |
| memset(&dsp_logbuff_work, 0, sizeof(dsp_logbuff_work)); |
| dsp_logbuff_wq = NULL; |
| } |
| } |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_sleep(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| pr_debug("%s\n", __func__); |
| |
| dsp->info = NULL; |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_wake(struct hifi4dsp_dsp *dsp) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| info = (struct hifi4dsp_info_t *)dsp->info; |
| pr_debug("%s\n", __func__); |
| |
| dsp->info = NULL; |
| return 0; |
| } |
| |
| static void hifi4dsp_driver_dsp_free(struct hifi4dsp_dsp *dsp) |
| { |
| pr_debug("%s\n", __func__); |
| |
| hifi4dsp_fw_free_all(dsp); |
| //kfree(NULL) is safe and check is probably not required |
| |
| kfree(dsp->dsp_fw); |
| dsp->dsp_fw = NULL; |
| |
| kfree(dsp->fw); |
| dsp->fw = NULL; |
| |
| kfree(dsp->ops); |
| dsp->ops = NULL; |
| |
| kfree(dsp->pdata); |
| dsp->pdata = NULL; |
| |
| kfree(dsp); |
| } |
| |
| /*transfer param from pdata to dsp*/ |
| static int hifi4dsp_driver_resource_map(struct hifi4dsp_dsp *dsp, |
| struct hifi4dsp_pdata *pdata) |
| { |
| int ret = 0; |
| |
| if (!pdata) { |
| pr_err("%s error\n", __func__); |
| ret = -1; |
| } |
| return ret; |
| } |
| |
| static int hifi4dsp_driver_init(struct hifi4dsp_dsp *dsp, |
| struct hifi4dsp_pdata *pdata) |
| { |
| struct device *dev; |
| int ret = -ENODEV; |
| |
| dev = dsp->dev; |
| pr_debug("%s\n", __func__); |
| ret = hifi4dsp_driver_resource_map(dsp, pdata); |
| if (ret < 0) { |
| dev_err(dev, "failed to map resources\n"); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static void hifi4dsp_driver_free(struct hifi4dsp_priv *priv) |
| { |
| pr_debug("%s\n", __func__); |
| if (priv->dsp) { |
| hifi4dsp_driver_dsp_free(priv->dsp); |
| priv->dsp = NULL; |
| } |
| if (priv->dsp_fw) { |
| priv->dsp_fw = NULL; |
| return; |
| } |
| } |
| |
| static int hifi4dsp_load_and_parse_fw(struct hifi4dsp_firmware *dsp_fw, |
| void *pinfo) |
| { |
| struct hifi4dsp_info_t *info; |
| |
| info = (struct hifi4dsp_info_t *)pinfo; |
| pr_debug("%s\n", __func__); |
| hifi4dsp_driver_load_fw(dsp_fw->dsp); |
| return 0; |
| } |
| |
| struct hifi4dsp_ops hifi4dsp_driver_dsp_ops = { |
| .boot = hifi4dsp_driver_dsp_boot, |
| .reset = hifi4dsp_driver_reset, |
| .sleep = hifi4dsp_driver_dsp_sleep, |
| .wake = hifi4dsp_driver_dsp_wake, |
| |
| .write = hifi4dsp_smem_write, |
| .read = hifi4dsp_smem_read, |
| .write64 = hifi4dsp_smem_write64, |
| .read64 = hifi4dsp_smem_read64, |
| .ram_read = hifi4dsp_memcpy_fromio_32, |
| .ram_write = hifi4dsp_memcpy_toio_32, |
| |
| .init = hifi4dsp_driver_init, |
| .free = hifi4dsp_driver_dsp_free, |
| .parse_fw = hifi4dsp_load_and_parse_fw, |
| }; |
| |
| static struct hifi4dsp_dsp_device hifi4dsp_dev = { |
| .ops = &hifi4dsp_driver_dsp_ops, |
| }; |
| |
| static struct hifi4dsp_priv *hifi4dsp_privdata(void) |
| { |
| return hifi4dsp_p[0]; |
| } |
| |
| static int hifi4dsp_platform_remove(struct platform_device *pdev) |
| { |
| struct hifi4dsp_priv *priv; |
| int id = 0, dsp_cnt = 0; |
| int ret = 0; |
| |
| ret = of_property_read_u32(pdev->dev.of_node, "dsp-cnt", &dsp_cnt); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve dsp-cnt\n"); |
| ret = -EINVAL; |
| goto dsp_cnt_error; |
| } |
| |
| hifi4_syslog_reomve(); |
| |
| priv = hifi4dsp_privdata(); |
| for (id = 0; id < dsp_cnt; id++) { |
| if (!priv) |
| continue; |
| hifi4dsp_driver_free(priv); |
| if (priv->dev) |
| device_destroy(priv->class, priv->dev->devt); |
| priv += 1; |
| } |
| kfree(priv); |
| for (id = 0; id < dsp_cnt; id++) |
| hifi4dsp_p[id] = NULL; |
| |
| return 0; |
| dsp_cnt_error: |
| return ret; |
| } |
| |
| static const struct file_operations hifi4dsp_miscdev_fops = { |
| .owner = THIS_MODULE, |
| .open = hifi4dsp_miscdev_open, |
| .read = NULL, |
| .write = NULL, |
| .release = hifi4dsp_miscdev_release, |
| .unlocked_ioctl = hifi4dsp_miscdev_unlocked_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = hifi4dsp_miscdev_compat_ioctl, |
| #endif |
| .mmap = hifi4dsp_miscdev_mmap, |
| }; |
| |
| static struct miscdevice hifi4dsp_miscdev[] = { |
| { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "hifi4dsp0", |
| .fops = &hifi4dsp_miscdev_fops, |
| }, |
| { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "hifi4dsp1", |
| .fops = &hifi4dsp_miscdev_fops, |
| } |
| }; |
| |
| static void *mm_vmap(phys_addr_t phys, unsigned long size, pgprot_t pgprotattr) |
| { |
| u32 offset, npages; |
| struct page **pages = NULL; |
| pgprot_t pgprot = pgprotattr; |
| void *vaddr; |
| int i; |
| |
| offset = offset_in_page(phys); |
| npages = DIV_ROUND_UP(size + offset, PAGE_SIZE); |
| |
| pages = vmalloc(sizeof(struct page *) * npages); |
| if (!pages) |
| return NULL; |
| for (i = 0; i < npages; i++) { |
| pages[i] = phys_to_page(phys); |
| phys += PAGE_SIZE; |
| } |
| |
| vaddr = vmap(pages, npages, VM_MAP, pgprot); |
| if (!vaddr) { |
| pr_err("vmaped fail, size: %d\n", |
| npages << PAGE_SHIFT); |
| vfree(pages); |
| return NULL; |
| } |
| vfree(pages); |
| pr_debug("[HIGH-MEM-MAP] pa(%lx) to va(%p), size: %d\n", |
| (unsigned long)phys, vaddr, npages << PAGE_SHIFT); |
| |
| return vaddr; |
| } |
| |
| /*of read clk_gate, clk*/ |
| static inline int of_read_dsp_irq(struct platform_device *pdev, int dsp_id) |
| { |
| int irq = -1; |
| |
| if (dsp_id == 0) |
| irq = of_irq_get_byname(pdev->dev.of_node, "irq_frm_dspa"); |
| else if (dsp_id == 1) |
| irq = of_irq_get_byname(pdev->dev.of_node, "irq_frm_dspb"); |
| |
| pr_debug("%s %s irq=%d\n", __func__, |
| (irq < 0) ? "error" : "successful", irq); |
| |
| return irq; |
| } |
| |
| /*of read clk_gate, clk*/ |
| static inline struct clk *of_read_dsp_clk(struct platform_device *pdev, |
| int dsp_id) |
| { |
| struct clk *p_clk = NULL; |
| char clk_name[20]; |
| |
| if (dsp_id == 0) { |
| strcpy(clk_name, "dspa_clk"); |
| p_clk = devm_clk_get(&pdev->dev, clk_name); |
| } else if (dsp_id == 1) { |
| strcpy(clk_name, "dspb_clk"); |
| p_clk = devm_clk_get(&pdev->dev, clk_name); |
| } |
| if (!p_clk) |
| pr_err("%s %s error\n", __func__, clk_name); |
| |
| return p_clk; |
| } |
| |
| static int hifi4dsp_runtime_resume(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int hifi4dsp_runtime_suspend(struct device *dev) |
| { |
| return 0; |
| } |
| |
| void get_dsp_baseaddr(struct platform_device *pdev) |
| { |
| struct resource *res; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get dspa base address\n"); |
| return; |
| } |
| g_regbases.dspa_addr = devm_ioremap_resource(&pdev->dev, res); |
| g_regbases.rega_size = resource_size(res); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get dspb base address\n"); |
| return; |
| } |
| g_regbases.dspb_addr = devm_ioremap_resource(&pdev->dev, res); |
| g_regbases.regb_size = resource_size(res); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get dsp hiu address\n"); |
| return; |
| } |
| //g_regbases.hiu_addr = devm_ioremap_resource(&pdev->dev, res); |
| g_regbases.hiu_addr = ioremap_nocache(res->start, resource_size(res)); |
| } |
| |
| void get_dsp_statusreg(struct platform_device *pdev, int dsp_cnt, |
| struct hifi4dsp_priv **hifi4dsp_p) |
| { |
| struct resource *res; |
| int i; |
| |
| for (i = 0; i < dsp_cnt; i++) { |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2 + i); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get dsp%d status register.\n", i); |
| return; |
| } |
| hifi4dsp_p[i]->dsp->status_reg = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(hifi4dsp_p[i]->dsp->status_reg)) |
| hifi4dsp_p[i]->dsp->status_reg = NULL; |
| } |
| } |
| |
| static struct hifi4dsp_pdata dsp_pdatas[] = {/*ARRAY_SIZE(dsp_pdatas)*/ |
| { |
| .name = "hifi4dsp0", |
| .clk_freq = 400 * 1000 * 1000, |
| }, |
| { |
| .name = "hifi4dsp1", |
| .clk_freq = 400 * 1000 * 1000, |
| }, |
| }; |
| |
| static int hifi4dsp_attach_pd(struct device *dev, int dsp_cnt) |
| { |
| struct hifi4dsp_dsp *dsp = dev_get_drvdata(dev); |
| struct device_link *link; |
| char *pd_name[2] = {"dspa", "dspb"}; |
| int i; |
| |
| if (dev->pm_domain) |
| return -1; |
| for (i = 0; i < dsp_cnt; i++) { |
| dsp += i; |
| dsp->pd_dsp = dev_pm_domain_attach_by_name(dev, pd_name[i]); |
| if (IS_ERR(dsp->pd_dsp)) |
| return PTR_ERR(dsp->pd_dsp); |
| if (!dsp->pd_dsp) |
| return -1; |
| link = device_link_add(dev, dsp->pd_dsp, |
| DL_FLAG_STATELESS | |
| DL_FLAG_PM_RUNTIME | |
| DL_FLAG_RPM_ACTIVE); |
| if (!link) { |
| dev_err(dev, "Failed to add device_link to %s pd.\n", |
| pd_name[i]); |
| return -EINVAL; |
| } |
| } |
| return 0; |
| } |
| |
| static int get_hifi_firmware_mem(struct reserved_mem *fwmem, struct platform_device *pdev) |
| { |
| int ret = -1; |
| struct device_node *mem_node; |
| struct reserved_mem *tmp = NULL; |
| struct page *cma_pages = NULL; |
| u32 dspsrambase = 0, dspsramsize = 0; |
| |
| /*parse sram fwmem*/ |
| ret = of_property_read_u32(pdev->dev.of_node, "dspsrambase", &dspsrambase); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve srambase\n"); |
| goto parse_cma; |
| } |
| pr_debug("of read dspsrambase=0x%08x\n", dspsrambase); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, "dspsramsize", &dspsramsize); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve dspsramsize\n"); |
| goto parse_cma; |
| } |
| pr_debug("of read dspsramsize=0x%08x\n", dspsramsize); |
| |
| fwmem->size = dspsramsize; |
| fwmem->base = dspsrambase; |
| fwmem->priv = "sram"; |
| pr_info("of get sram memory region success[0x%lx 0x%lx]\n", |
| (unsigned long)fwmem->base, (unsigned long)fwmem->size); |
| return 0; |
| |
| /*parse ddr fwmem*/ |
| parse_cma: |
| ret = of_reserved_mem_device_init(&pdev->dev); |
| if (ret) { |
| pr_debug("reserved memory init fail:%d\n", ret); |
| goto out; |
| } |
| mem_node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); |
| if (!mem_node) { |
| ret = -1; |
| goto out; |
| } |
| tmp = of_reserved_mem_lookup(mem_node); |
| of_node_put(mem_node); |
| if (tmp) { |
| fwmem->size = tmp->size; |
| cma_pages = cma_alloc(dev_get_cma_area(&pdev->dev), |
| PAGE_ALIGN(fwmem->size) >> PAGE_SHIFT, 0, false); |
| if (cma_pages) { |
| fwmem->base = page_to_phys(cma_pages); |
| fwmem->priv = "ddr"; |
| pr_info("of read fwmem phys = [0x%lx 0x%lx]\n", |
| (unsigned long)fwmem->base, (unsigned long)fwmem->size); |
| } |
| } else { |
| ret = -1; |
| dev_err(&pdev->dev, "Can't retrieve reserve memory region\n"); |
| } |
| out: |
| return ret; |
| } |
| |
| static int get_hifi_share_mem(struct reserved_mem *shmem, struct platform_device *pdev) |
| { |
| struct resource *dsp_shm_res; |
| |
| /*parse shmem*/ |
| dsp_shm_res = platform_get_resource(pdev, IORESOURCE_MEM, 4); |
| if (!dsp_shm_res) { |
| dev_err(&pdev->dev, "failed to get dsp share memory resource.\n"); |
| return -1; |
| } |
| shmem->base = dsp_shm_res->start; |
| shmem->size = resource_size(dsp_shm_res); |
| pr_info("of read shmem phys = [0x%lx 0x%lx]\n", |
| (unsigned long)shmem->base, (unsigned long)shmem->size); |
| |
| return 0; |
| } |
| |
| static void hifi_fw_mem_update(int dsp_id, phys_addr_t *base, int *size) |
| { |
| if (!strcmp(hifi4_rmem.priv, "sram")) { |
| *base = hifi4_rmem.base + (dsp_id == 0 ? 0 : hifi4_rmem.size >> 1); |
| *size = hifi4_rmem.size >> 1; |
| } |
| } |
| |
| static void *hifi_fw_mem_map(phys_addr_t base, int size) |
| { |
| if (!strcmp(hifi4_rmem.priv, "sram")) |
| return ioremap_nocache(base, size); |
| else |
| return mm_vmap(base, size, pgprot_dmacoherent(PAGE_KERNEL)); |
| } |
| |
| void *get_hifi_fw_mem_type(void) |
| { |
| return hifi4_rmem.priv; |
| } |
| |
| static int hifi4dsp_clk_to_24M(struct hifi4dsp_dsp *dsp) |
| { |
| int ret; |
| |
| if (!dsp->dsp_clk) { |
| pr_err("dsp_clk=NULL\n"); |
| return -EINVAL; |
| } |
| |
| ret = clk_set_rate(dsp->dsp_clk, SUSPEND_CLK_FREQ); |
| if (ret) { |
| pr_err("%s: error in setting dsp clk rate!\n", |
| __func__); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int hifi4dsp_clk_to_normal(struct hifi4dsp_dsp *dsp) |
| { |
| int ret; |
| |
| if (!dsp->dsp_clk) { |
| pr_err("dsp_clk=NULL\n"); |
| return -EINVAL; |
| } |
| |
| ret = clk_set_rate(dsp->dsp_clk, dsp->freq); |
| if (ret) { |
| pr_err("%s: error in setting dsp clk rate!\n", |
| __func__); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_suspend(struct hifi4dsp_dsp *dsp) |
| { |
| char message[30]; |
| |
| strcpy(message, "SCPI_CMD_HIFI4SUSPEND"); |
| |
| if (!dsp->id) |
| scpi_send_data(message, sizeof(message), |
| SCPI_DSPA, SCPI_CMD_HIFI4SUSPEND, |
| message, sizeof(message)); |
| else |
| scpi_send_data(message, sizeof(message), |
| SCPI_DSPB, SCPI_CMD_HIFI4SUSPEND, |
| message, sizeof(message)); |
| hifi4dsp_clk_to_24M(dsp); |
| |
| return 0; |
| } |
| |
| static int hifi4dsp_driver_dsp_resume(struct hifi4dsp_dsp *dsp) |
| { |
| char message[30]; |
| |
| /*switch dsp clk to normal*/ |
| hifi4dsp_clk_to_normal(dsp); |
| |
| strcpy(message, "SCPI_CMD_HIFI4RESUME"); |
| |
| if (!dsp->id) |
| scpi_send_data(message, sizeof(message), |
| SCPI_DSPA, SCPI_CMD_HIFI4RESUME, |
| message, sizeof(message)); |
| else |
| scpi_send_data(message, sizeof(message), |
| SCPI_DSPB, SCPI_CMD_HIFI4RESUME, |
| message, sizeof(message)); |
| return 0; |
| } |
| |
| int of_read_dsp_cnt(struct platform_device *pdev) |
| { |
| int ret; |
| int dsp_cnt; |
| |
| ret = of_property_read_u32(pdev->dev.of_node, "dsp-cnt", &dsp_cnt); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve dsp-cnt\n"); |
| return -EINVAL; |
| } |
| pr_debug("%s of read dsp-cnt=%d\n", __func__, dsp_cnt); |
| |
| return dsp_cnt; |
| } |
| |
| static int dsp_suspend(struct device *dev) |
| { |
| int dsp_cnt, i; |
| struct hifi4dsp_dsp *dsp = NULL; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| dsp_cnt = of_read_dsp_cnt(pdev); |
| |
| if (dsp_cnt > 0) { |
| for (i = 0; i < dsp_cnt; i++) { |
| dsp = hifi4dsp_p[i]->dsp; |
| if (dsp->dspstarted == 1 && dsp->suspend_resume_support) { |
| pr_debug("AP send suspend cmd to dsp...\n"); |
| hifi4dsp_driver_dsp_suspend(dsp); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int dsp_resume(struct device *dev) |
| { |
| int dsp_cnt, i; |
| struct hifi4dsp_dsp *dsp = NULL; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| dsp_cnt = of_read_dsp_cnt(pdev); |
| |
| if (dsp_cnt > 0) { |
| for (i = 0; i < dsp_cnt; i++) { |
| dsp = hifi4dsp_p[i]->dsp; |
| if (dsp->dspstarted == 1 && dsp->suspend_resume_support) { |
| pr_debug("AP send resume cmd to dsp...\n"); |
| hifi4dsp_driver_dsp_resume(dsp); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static ssize_t suspend_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| char message[30]; |
| struct hifi4dsp_dsp *dsp = NULL; |
| int dspid; |
| |
| strcpy(message, "SCPI_CMD_HIFI4RESUME"); |
| |
| if (!strncmp(buf, "hifi4a", 6)) { |
| dspid = 0; |
| } else if (!strncmp(buf, "hifi4b", 6)) { |
| dspid = 1; |
| } else { |
| pr_err("please input the right args : hifi4a/hifi4b\n"); |
| return 0; |
| } |
| dsp = hifi4dsp_p[dspid]->dsp; |
| |
| if (!dsp || !dsp->suspend_resume_support) |
| return 0; |
| |
| scpi_send_data(message, sizeof(message), |
| dspid ? SCPI_DSPB : SCPI_DSPA, SCPI_CMD_HIFI4SUSPEND, |
| message, sizeof(message)); |
| hifi4dsp_clk_to_24M(dsp); |
| |
| return size; |
| } |
| static DEVICE_ATTR_WO(suspend); |
| |
| static ssize_t resume_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| char message[30]; |
| struct hifi4dsp_dsp *dsp = NULL; |
| int dspid; |
| |
| strcpy(message, "SCPI_CMD_HIFI4RESUME"); |
| |
| if (!strncmp(buf, "hifi4a", 6)) { |
| dspid = 0; |
| } else if (!strncmp(buf, "hifi4b", 6)) { |
| dspid = 1; |
| } else { |
| pr_err("please input the right args : hifi4a/hifi4b\n"); |
| return 0; |
| } |
| dsp = hifi4dsp_p[dspid]->dsp; |
| |
| if (!dsp || !dsp->suspend_resume_support) |
| return 0; |
| |
| hifi4dsp_clk_to_normal(dsp); |
| scpi_send_data(message, sizeof(message), |
| dspid ? SCPI_DSPB : SCPI_DSPA, SCPI_CMD_HIFI4RESUME, |
| message, sizeof(message)); |
| |
| return size; |
| } |
| static DEVICE_ATTR_WO(resume); |
| |
| static struct dsp_ring_buffer *dsp_init_ring_buffer(unsigned int buffer, |
| unsigned int size) |
| { |
| struct dsp_ring_buffer *b = (struct dsp_ring_buffer *)mm_vmap(buffer, |
| size, pgprot_dmacoherent(PAGE_KERNEL)); |
| |
| if (!b || size < sizeof(struct dsp_ring_buffer)) |
| return NULL; |
| if (b->magic == DSP_LOGBUFF_MAGIC) { |
| pr_err("rbuf:magic is ok\n"); |
| return b; |
| } |
| |
| b->magic = DSP_LOGBUFF_MAGIC; |
| b->basepaddr = buffer; |
| b->size = size - sizeof(struct dsp_ring_buffer) + 4; |
| b->head = 0; |
| b->tail = 0; |
| |
| return b; |
| } |
| |
| static int hifi4dsp_platform_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| int i = 0, id = 0; |
| unsigned int dsp_cnt = 0; |
| unsigned int dspaoffset, dspboffset; |
| struct hifi4dsp_priv *priv; |
| struct hifi4dsp_dsp *dsp; |
| struct hifi4dsp_pdata *pdata; |
| struct hifi4dsp_miscdev_t *p_dsp_miscdev; |
| struct miscdevice *pmscdev; |
| enum dsp_start_mode startmode; |
| u32 optimize_longcall[2]; |
| u32 sram_remap_addr[4]; |
| u32 pm_support[2]; |
| u32 dsp_logbuff[4] = {0}; |
| |
| phys_addr_t hifi4base; |
| int hifi4size; |
| void *fw_addr = NULL; |
| |
| struct hifi4dsp_firmware *dsp_firmware; |
| struct firmware *fw = NULL; |
| |
| struct device_node *np; |
| struct clk *dsp_clk = NULL; |
| |
| struct hifi4dsp_info_t *hifi_info = NULL; |
| |
| np = pdev->dev.of_node; |
| |
| /*dsp boot offset */ |
| ret = of_property_read_u32(np, "dsp-cnt", &dsp_cnt); |
| if (ret < 0 || dsp_cnt <= 0) { |
| dev_err(&pdev->dev, "Can't retrieve dsp-cnt\n"); |
| goto err1; |
| } |
| pr_debug("%s of read dsp-cnt=%d\n", __func__, dsp_cnt); |
| |
| ret = of_property_read_u32(np, "dsp-start-mode", &startmode); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "can't retrieve dsp-start-mode\n"); |
| goto err1; |
| } |
| pr_debug("%s of read dsp_start_mode = %d\n", __func__, startmode); |
| |
| ret = of_property_read_u32(np, "dspaoffset", &dspaoffset); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve dspaoffset\n"); |
| goto err1; |
| } |
| pr_debug("%s of read dspaoffset=0x%08x\n", __func__, dspaoffset); |
| |
| ret = of_property_read_u32(np, "dspboffset", &dspboffset); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve dspboffset\n"); |
| goto err1; |
| } |
| pr_debug("%s of read dspboffset=0x%08x\n", __func__, dspboffset); |
| |
| ret = of_property_read_u32(np, "dsp-monitor-period-ms", &dsp_monitor_period_ms); |
| if (ret < 0) |
| dev_err(&pdev->dev, "Can't retrieve dsp-monitor-period-ms\n"); |
| |
| /*boot from DDR or SRAM or ...*/ |
| ret = of_property_read_u32(np, "bootlocation", &bootlocation); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve bootlocation\n"); |
| goto err1; |
| } |
| pr_debug("%s of read dsp bootlocation=%x\n", __func__, bootlocation); |
| if (bootlocation == 1) { |
| pr_info("Dsp boot from DDR !\n"); |
| } else if (bootlocation == 2) { |
| ret = of_property_read_u32(np, "boot_sram_addr", |
| &boot_sram_addr); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve boot_sram_addr\n"); |
| goto err1; |
| } |
| ret = of_property_read_u32(np, "boot_sram_size", |
| &boot_sram_size); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Can't retrieve boot_sram_size\n"); |
| goto err1; |
| } |
| pr_info("Dsp boot from SRAM !boot addr : 0x%08x, allocate size: 0x%08x\n", |
| boot_sram_addr, boot_sram_size); |
| g_regbases.sram_base = ioremap_nocache(boot_sram_addr, |
| boot_sram_size); |
| } |
| |
| ret = of_property_read_u32_array(np, "optimize_longcall", &optimize_longcall[0], 2); |
| if (ret) |
| pr_debug("can't get optimize_longcall\n"); |
| ret = of_property_read_u32_array(np, "sram_remap_addr", &sram_remap_addr[0], 4); |
| if (ret) |
| pr_debug("can't get sram_remap_addr\n"); |
| ret = of_property_read_u32_array(np, "suspend_resume_support", &pm_support[0], 2); |
| if (ret) |
| pr_debug("can't get suspend_resume_support\n"); |
| ret = of_property_read_u32_array(np, "dsp_logbuff", &dsp_logbuff[0], 4); |
| if (ret) |
| pr_debug("didn't support dsp log buffer.\n"); |
| else |
| pr_debug("[dsp_logbuff][0x%x 0x%x 0x%x 0x%x]\n", |
| dsp_logbuff[0], dsp_logbuff[1], |
| dsp_logbuff[2], dsp_logbuff[3]); |
| |
| ret = of_property_read_u32(np, "logbuff-polling-period-ms", |
| &dsp_logbuff_polling_period_ms); |
| if (ret < 0) |
| dev_err(&pdev->dev, "Can't retrieve logbuff-polling-period-ms\n"); |
| |
| /*init hifi4dsp_dsp*/ |
| dsp = kcalloc(dsp_cnt, sizeof(*dsp), GFP_KERNEL); |
| if (!dsp) |
| goto err2; |
| |
| /*init hifi4dsp_info_t*/ |
| hifi_info = kzalloc(sizeof(*hifi_info), GFP_KERNEL); |
| if (!hifi_info) |
| goto hifi_info_malloc_error; |
| |
| /*init miscdev_t, miscdevice*/ |
| p_dsp_miscdev = kcalloc(dsp_cnt, sizeof(struct hifi4dsp_miscdev_t), |
| GFP_KERNEL); |
| if (!p_dsp_miscdev) { |
| HIFI4DSP_PRNT("kzalloc for p_dsp_miscdev error\n"); |
| goto miscdev_malloc_error; |
| } |
| |
| /*init hifi4dsp_priv*/ |
| priv = kcalloc(dsp_cnt, sizeof(struct hifi4dsp_priv), |
| GFP_KERNEL); |
| |
| if (!priv) { |
| HIFI4DSP_PRNT("kzalloc for hifi4dsp_priv error\n"); |
| goto priv_malloc_error; |
| } |
| |
| /*init hifi4dsp_pdata*/ |
| pdata = kcalloc(dsp_cnt, sizeof(struct hifi4dsp_pdata), GFP_KERNEL); |
| if (!pdata) { |
| HIFI4DSP_PRNT("kzalloc for hifi4dsp_pdata error\n"); |
| goto pdata_malloc_error; |
| } |
| |
| /*init dsp firmware*/ |
| dsp_firmware = kcalloc(dsp_cnt, sizeof(*dsp_firmware), GFP_KERNEL); |
| if (!dsp_firmware) |
| goto dsp_firmware_malloc_error; |
| |
| /*init real dsp firmware*/ |
| fw = kzalloc(sizeof(*fw), GFP_KERNEL); |
| if (!fw) |
| goto real_fw_malloc_error; |
| |
| /*get regbase*/ |
| get_dsp_baseaddr(pdev); |
| |
| if (get_hifi_firmware_mem(&hifi4_rmem, pdev)) |
| goto err3; |
| if (!get_hifi_share_mem(&hifi_shmem, pdev)) { |
| dsp->addr.smem_paddr = hifi_shmem.base; |
| dsp->addr.smem_size = hifi_shmem.size; |
| if (dsp->addr.smem_paddr && dsp->addr.smem_size) { |
| dsp->addr.smem = mm_vmap(dsp->addr.smem_paddr, dsp->addr.smem_size, |
| pgprot_dmacoherent(PAGE_KERNEL)); |
| pr_info("sharemem map phys:0x%lx-->virt:0x%lx\n", |
| (unsigned long)dsp->addr.smem_paddr, (unsigned long)dsp->addr.smem); |
| } |
| } |
| |
| if (dsp_cnt > 1) { |
| platform_set_drvdata(pdev, dsp); |
| ret = hifi4dsp_attach_pd(&pdev->dev, dsp_cnt); |
| if (ret < 0) |
| goto err3; |
| } |
| /*init hifi4 syslog*/ |
| create_hifi4_syslog(); |
| mutex_init(&hifi4dsp_flock); |
| |
| for (i = 0; i < dsp_cnt; i++) { |
| id = i; |
| p_dsp_miscdev += i; |
| priv += i; |
| pdata += i; |
| dsp += i; |
| dsp_firmware += i; |
| pr_info("\nregister dsp-%d start\n", id); |
| |
| /*get boot address*/ |
| pr_debug("reserved_mem :base:0x%llx, size:0x%lx\n", |
| (unsigned long long)hifi4_rmem.base, |
| (unsigned long)hifi4_rmem.size); |
| hifi4base = hifi4_rmem.base + (id == 0 ? |
| dspaoffset : |
| dspboffset); |
| hifi4size = |
| (id == 0 ? |
| (dspboffset - dspaoffset - hifi_shmem.size) : |
| ((unsigned long)hifi4_rmem.size - dspboffset)); |
| hifi_fw_mem_update(id, &hifi4base, &hifi4size); |
| fw_addr = hifi_fw_mem_map(hifi4base, hifi4size); |
| pr_info("hifi4dsp%d, firmware :base:0x%llx, size:0x%x, virt:%lx\n", |
| id, |
| (unsigned long long)hifi4base, |
| hifi4size, |
| (unsigned long)fw_addr); |
| |
| /*init miscdevice pmscdev and register*/ |
| memcpy(&p_dsp_miscdev->dsp_miscdev, &hifi4dsp_miscdev[id], |
| sizeof(struct miscdevice)); |
| pmscdev = &p_dsp_miscdev->dsp_miscdev; |
| |
| //pmscdev = &hifi4dsp_miscdev[id]; |
| ret = misc_register(pmscdev); |
| if (ret) { |
| pr_err("register vad_miscdev error\n"); |
| goto err3; |
| } |
| |
| /*add miscdevice to priv*/ |
| priv->dev = pmscdev->this_device; |
| |
| /*init hifi4dsp_firmware and add to hifi4dsp_dsp */ |
| dsp_firmware->paddr = hifi4base; |
| dsp_firmware->size = hifi4size; |
| dsp_firmware->id = id; |
| dsp_firmware->buf = fw_addr; |
| strcpy(dsp_firmware->name, "amlogic_firmware"); |
| |
| /*init hifi4dsp_miscdev_t ->priv*/ |
| p_dsp_miscdev->priv = priv; |
| if (!(p_dsp_miscdev->priv)) |
| pr_info("register dsp _p_dsp_miscdev->priv alloc error"); |
| else |
| pr_info("register dsp _p_dsp_miscdev->priv alloc success"); |
| |
| /*of read clk and add to priv*/ |
| dsp_clk = of_read_dsp_clk(pdev, id); |
| priv->p_clk = dsp_clk; |
| |
| /*init hifidsp_pdata save in *hifi4dsp_data and add to priv*/ |
| pdata = &dsp_pdatas[i]; |
| pdata->fw_paddr = hifi4base; |
| pdata->fw_buf = fw_addr; |
| pdata->fw_max_size = hifi4size; |
| pdata->reg_size = (id == 0 ? |
| g_regbases.rega_size : g_regbases.regb_size); |
| pdata->reg = (id == 0 ? |
| g_regbases.dspa_addr : g_regbases.dspb_addr); |
| pdata->id = id; |
| priv->pdata = pdata; |
| |
| /*add hifi4dsp_dsp_device to priv*/ |
| priv->dsp_dev = &hifi4dsp_dev; |
| |
| /*initial hifi4dsp_dsp and add to priv*/ |
| mutex_init(&dsp->mutex); |
| spin_lock_init(&dsp->spinlock); |
| spin_lock_init(&dsp->fw_spinlock); |
| INIT_LIST_HEAD(&dsp->fw_list); |
| dsp->dsp_clk = dsp_clk; |
| dsp->fw = fw; |
| dsp->dsp_fw = dsp_firmware; |
| dsp->id = id; |
| dsp->freq = pdata->clk_freq; |
| dsp->irq = pdata->irq; |
| dsp->major_id = MAJOR(priv->dev->devt); |
| dsp->dev = priv->dev; |
| dsp->pdata = pdata; |
| dsp->info = hifi_info; |
| dsp->dsp_dev = priv->dsp_dev; |
| dsp->ops = priv->dsp_dev->ops; |
| dsp->start_mode = startmode; |
| dsp->dsphang = 0; |
| dsp->optimize_longcall = optimize_longcall[id]; |
| dsp->sram_remap_addr[0] = sram_remap_addr[2 * id]; |
| dsp->sram_remap_addr[1] = sram_remap_addr[2 * id + 1]; |
| dsp->suspend_resume_support = pm_support[id]; |
| if (dsp_logbuff[2 * id]) |
| dsp->logbuff = dsp_init_ring_buffer(dsp_logbuff[2 * id], |
| dsp_logbuff[2 * id + 1]); |
| else |
| dsp->logbuff = NULL; |
| priv->dsp = dsp; |
| |
| hifi4dsp_p[i] = priv; |
| |
| if (dsp_cnt > 1) { |
| pm_runtime_put_sync_suspend(dsp->pd_dsp); |
| } else { |
| pm_runtime_enable(&pdev->dev); |
| priv->dev_pd = &pdev->dev; |
| } |
| pr_info("register dsp-%d done\n", id); |
| } |
| get_dsp_statusreg(pdev, dsp_cnt, hifi4dsp_p); |
| device_create_file(&pdev->dev, &dev_attr_suspend); |
| device_create_file(&pdev->dev, &dev_attr_resume); |
| pr_info("%s done\n", __func__); |
| return 0; |
| |
| err3: |
| kfree(fw); |
| real_fw_malloc_error: |
| kfree(dsp_firmware); |
| dsp_firmware_malloc_error: |
| kfree(pdata); |
| pdata_malloc_error: |
| kfree(priv); |
| priv_malloc_error: |
| kfree(p_dsp_miscdev); |
| miscdev_malloc_error: |
| kfree(hifi_info); |
| hifi_info_malloc_error: |
| kfree(dsp); |
| err2: |
| return -ENOMEM; |
| err1: |
| return -EINVAL; |
| } |
| |
| static const struct of_device_id hifi4dsp_device_id[] = { |
| { |
| .compatible = "amlogic, hifi4dsp", |
| }, |
| {}, |
| }; |
| |
| static const struct dev_pm_ops hifi4dsp_pm_ops = { |
| SET_RUNTIME_PM_OPS(hifi4dsp_runtime_suspend, |
| hifi4dsp_runtime_resume, NULL) |
| .suspend = dsp_suspend, |
| .resume = dsp_resume, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, hifi4dsp_device_id); |
| |
| static struct platform_driver hifi4dsp_platform_driver = { |
| .driver = { |
| .name = "hifi4dsp", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(hifi4dsp_device_id), |
| .pm = &hifi4dsp_pm_ops, |
| }, |
| .probe = hifi4dsp_platform_probe, |
| .remove = hifi4dsp_platform_remove, |
| }; |
| module_platform_driver(hifi4dsp_platform_driver); |
| |
| MODULE_AUTHOR("Amlogic"); |
| MODULE_DESCRIPTION("HiFi DSP Module Driver"); |
| MODULE_LICENSE("GPL v2"); |