/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include "wcd-dsp-utils.h" /* Forward declarations */ static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type); /* Component related macros */ #define WDSP_GET_COMPONENT(wdsp, x) (&(wdsp->cmpnts[x])) #define WDSP_GET_CMPNT_TYPE_STR(x) wdsp_get_cmpnt_type_string(x) /* * These #defines indicate the bit number in status field * for each of the status. If bit is set, it indicates * the status as done, else if bit is not set, it indicates * the status is either failed or not done. */ #define WDSP_STATUS_INITIALIZED BIT(0) #define WDSP_STATUS_CODE_DLOADED BIT(1) #define WDSP_STATUS_DATA_DLOADED BIT(2) #define WDSP_STATUS_BOOTED BIT(3) /* Helper macros for printing wdsp messages */ #define WDSP_ERR(wdsp, fmt, ...) \ dev_err(wdsp->mdev, "%s: " fmt "\n", __func__, ##__VA_ARGS__) #define WDSP_DBG(wdsp, fmt, ...) \ dev_dbg(wdsp->mdev, "%s: " fmt "\n", __func__, ##__VA_ARGS__) /* Helper macros for locking */ #define WDSP_MGR_MUTEX_LOCK(wdsp, lock) \ { \ WDSP_DBG(wdsp, "mutex_lock(%s)", \ __stringify_1(lock)); \ mutex_lock(&lock); \ } #define WDSP_MGR_MUTEX_UNLOCK(wdsp, lock) \ { \ WDSP_DBG(wdsp, "mutex_unlock(%s)", \ __stringify_1(lock)); \ mutex_unlock(&lock); \ } /* Helper macros for using status mask */ #define WDSP_SET_STATUS(wdsp, state) \ { \ wdsp->status |= state; \ WDSP_DBG(wdsp, "set 0x%lx, new_state = 0x%x", \ state, wdsp->status); \ } #define WDSP_CLEAR_STATUS(wdsp, state) \ { \ wdsp->status &= (~state); \ WDSP_DBG(wdsp, "clear 0x%lx, new_state = 0x%x", \ state, wdsp->status); \ } #define WDSP_STATUS_IS_SET(wdsp, state) (wdsp->status & state) /* SSR relate status macros */ #define WDSP_SSR_STATUS_WDSP_READY BIT(0) #define WDSP_SSR_STATUS_CDC_READY BIT(1) #define WDSP_SSR_STATUS_READY \ (WDSP_SSR_STATUS_WDSP_READY | WDSP_SSR_STATUS_CDC_READY) #define WDSP_SSR_READY_WAIT_TIMEOUT (10 * HZ) enum wdsp_ssr_type { /* Init value, indicates there is no SSR in progress */ WDSP_SSR_TYPE_NO_SSR = 0, /* * Indicates WDSP crashed. The manager driver internally * decides when to perform WDSP restart based on the * users of wdsp. Hence there is no explicit WDSP_UP. */ WDSP_SSR_TYPE_WDSP_DOWN, /* Indicates codec hardware is down */ WDSP_SSR_TYPE_CDC_DOWN, /* Indicates codec hardware is up, trigger to restart WDSP */ WDSP_SSR_TYPE_CDC_UP, }; struct wdsp_cmpnt { /* OF node of the phandle */ struct device_node *np; /* * Child component's dev_name, should be set in DT for the child's * phandle if child's dev->of_node does not match the phandle->of_node */ const char *cdev_name; /* Child component's device node */ struct device *cdev; /* Private data that component may want back on callbacks */ void *priv_data; /* Child ops */ struct wdsp_cmpnt_ops *ops; }; struct wdsp_ramdump_data { /* Ramdump device */ void *rd_dev; /* DMA address of the dump */ dma_addr_t rd_addr; /* Virtual address of the dump */ void *rd_v_addr; /* Data provided through error interrupt */ struct wdsp_err_signal_arg err_data; }; struct wdsp_mgr_priv { /* Manager driver's struct device pointer */ struct device *mdev; /* Match struct for component framework */ struct component_match *match; /* Manager's ops/function callbacks */ struct wdsp_mgr_ops *ops; /* Array to store information for all expected components */ struct wdsp_cmpnt cmpnts[WDSP_CMPNT_TYPE_MAX]; /* The filename of image to be downloaded */ const char *img_fname; /* Keeps track of current state of manager driver */ u32 status; /* Work to load the firmware image after component binding */ struct work_struct load_fw_work; /* List of segments in image to be downloaded */ struct list_head *seg_list; /* Base address of the image in memory */ u32 base_addr; /* Instances using dsp */ int dsp_users; /* Lock for serializing ops called by components */ struct mutex api_mutex; struct wdsp_ramdump_data dump_data; /* SSR related */ enum wdsp_ssr_type ssr_type; struct mutex ssr_mutex; struct work_struct ssr_work; u16 ready_status; struct completion ready_compl; /* Debugfs related */ struct dentry *entry; bool panic_on_error; }; static char *wdsp_get_ssr_type_string(enum wdsp_ssr_type type) { switch (type) { case WDSP_SSR_TYPE_NO_SSR: return "NO_SSR"; case WDSP_SSR_TYPE_WDSP_DOWN: return "WDSP_DOWN"; case WDSP_SSR_TYPE_CDC_DOWN: return "CDC_DOWN"; case WDSP_SSR_TYPE_CDC_UP: return "CDC_UP"; default: pr_err("%s: Invalid ssr_type %d\n", __func__, type); return "Invalid"; } } static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type type) { switch (type) { case WDSP_CMPNT_CONTROL: return "control"; case WDSP_CMPNT_IPC: return "ipc"; case WDSP_CMPNT_TRANSPORT: return "transport"; default: pr_err("%s: Invalid component type %d\n", __func__, type); return "Invalid"; } } static void __wdsp_clr_ready_locked(struct wdsp_mgr_priv *wdsp, u16 value) { wdsp->ready_status &= ~(value); WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status); } static void __wdsp_set_ready_locked(struct wdsp_mgr_priv *wdsp, u16 value, bool mark_complete) { wdsp->ready_status |= value; WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status); if (mark_complete && wdsp->ready_status == WDSP_SSR_STATUS_READY) { WDSP_DBG(wdsp, "marking ready completion"); complete(&wdsp->ready_compl); } } static void wdsp_broadcast_event_upseq(struct wdsp_mgr_priv *wdsp, enum wdsp_event_type event, void *data) { struct wdsp_cmpnt *cmpnt; int i; for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) { cmpnt = WDSP_GET_COMPONENT(wdsp, i); if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler) cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data, event, data); } } static void wdsp_broadcast_event_downseq(struct wdsp_mgr_priv *wdsp, enum wdsp_event_type event, void *data) { struct wdsp_cmpnt *cmpnt; int i; for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) { cmpnt = WDSP_GET_COMPONENT(wdsp, i); if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler) cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data, event, data); } } static int wdsp_unicast_event(struct wdsp_mgr_priv *wdsp, enum wdsp_cmpnt_type type, enum wdsp_event_type event, void *data) { struct wdsp_cmpnt *cmpnt; int ret; cmpnt = WDSP_GET_COMPONENT(wdsp, type); if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler) { ret = cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data, event, data); } else { WDSP_ERR(wdsp, "not valid event_handler for %s", WDSP_GET_CMPNT_TYPE_STR(type)); ret = -EINVAL; } return ret; } static void wdsp_deinit_components(struct wdsp_mgr_priv *wdsp) { struct wdsp_cmpnt *cmpnt; int i; for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) { cmpnt = WDSP_GET_COMPONENT(wdsp, i); if (cmpnt && cmpnt->ops && cmpnt->ops->deinit) cmpnt->ops->deinit(cmpnt->cdev, cmpnt->priv_data); } } static int wdsp_init_components(struct wdsp_mgr_priv *wdsp) { struct wdsp_cmpnt *cmpnt; int fail_idx = WDSP_CMPNT_TYPE_MAX; int i, ret = 0; for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) { cmpnt = WDSP_GET_COMPONENT(wdsp, i); /* Init is allowed to be NULL */ if (!cmpnt->ops || !cmpnt->ops->init) continue; ret = cmpnt->ops->init(cmpnt->cdev, cmpnt->priv_data); if (ret) { WDSP_ERR(wdsp, "Init failed (%d) for component %s", ret, WDSP_GET_CMPNT_TYPE_STR(i)); fail_idx = i; break; } } if (fail_idx < WDSP_CMPNT_TYPE_MAX) { /* Undo init for already initialized components */ for (i = fail_idx - 1; i >= 0; i--) { struct wdsp_cmpnt *cmpnt = WDSP_GET_COMPONENT(wdsp, i); if (cmpnt->ops && cmpnt->ops->deinit) cmpnt->ops->deinit(cmpnt->cdev, cmpnt->priv_data); } } else { wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_INIT, NULL); } return ret; } static int wdsp_load_each_segment(struct wdsp_mgr_priv *wdsp, struct wdsp_img_segment *seg) { struct wdsp_img_section img_section; int ret; WDSP_DBG(wdsp, "base_addr 0x%x, split_fname %s, load_addr 0x%x, size 0x%zx", wdsp->base_addr, seg->split_fname, seg->load_addr, seg->size); if (seg->load_addr < wdsp->base_addr) { WDSP_ERR(wdsp, "Invalid addr 0x%x, base_addr = 0x%x", seg->load_addr, wdsp->base_addr); return -EINVAL; } img_section.addr = seg->load_addr - wdsp->base_addr; img_section.size = seg->size; img_section.data = seg->data; ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_TRANSPORT, WDSP_EVENT_DLOAD_SECTION, &img_section); if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "Failed, err = %d for base_addr = 0x%x split_fname = %s, load_addr = 0x%x, size = 0x%zx", ret, wdsp->base_addr, seg->split_fname, seg->load_addr, seg->size); return ret; } static int wdsp_download_segments(struct wdsp_mgr_priv *wdsp, unsigned int type) { struct wdsp_cmpnt *ctl; struct wdsp_img_segment *seg = NULL; enum wdsp_event_type pre, post; long status; int ret; ctl = WDSP_GET_COMPONENT(wdsp, WDSP_CMPNT_CONTROL); if (type == WDSP_ELF_FLAG_RE) { pre = WDSP_EVENT_PRE_DLOAD_CODE; post = WDSP_EVENT_POST_DLOAD_CODE; status = WDSP_STATUS_CODE_DLOADED; } else if (type == WDSP_ELF_FLAG_WRITE) { pre = WDSP_EVENT_PRE_DLOAD_DATA; post = WDSP_EVENT_POST_DLOAD_DATA; status = WDSP_STATUS_DATA_DLOADED; } else { WDSP_ERR(wdsp, "Invalid type %u", type); return -EINVAL; } ret = wdsp_get_segment_list(ctl->cdev, wdsp->img_fname, type, wdsp->seg_list, &wdsp->base_addr); if (IS_ERR_VALUE(ret) || list_empty(wdsp->seg_list)) { WDSP_ERR(wdsp, "Error %d to get image segments for type %d", ret, type); wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_DLOAD_FAILED, NULL); goto done; } /* Notify all components that image is about to be downloaded */ wdsp_broadcast_event_upseq(wdsp, pre, NULL); /* Go through the list of segments and download one by one */ list_for_each_entry(seg, wdsp->seg_list, list) { ret = wdsp_load_each_segment(wdsp, seg); if (IS_ERR_VALUE(ret)) { wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_DLOAD_FAILED, NULL); goto dload_error; } } WDSP_SET_STATUS(wdsp, status); /* Notify all components that image is downloaded */ wdsp_broadcast_event_downseq(wdsp, post, NULL); dload_error: wdsp_flush_segment_list(wdsp->seg_list); done: return ret; } static int wdsp_init_and_dload_code_sections(struct wdsp_mgr_priv *wdsp) { int ret; bool is_initialized; is_initialized = WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_INITIALIZED); if (!is_initialized) { /* Components are not initialized yet, initialize them */ ret = wdsp_init_components(wdsp); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "INIT failed, err = %d", ret); goto done; } WDSP_SET_STATUS(wdsp, WDSP_STATUS_INITIALIZED); } /* Download the read-execute sections of image */ ret = wdsp_download_segments(wdsp, WDSP_ELF_FLAG_RE); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Error %d to download code sections", ret); goto done; } done: return ret; } static void wdsp_load_fw_image(struct work_struct *work) { struct wdsp_mgr_priv *wdsp; int ret; wdsp = container_of(work, struct wdsp_mgr_priv, load_fw_work); if (!wdsp) { pr_err("%s: Invalid private_data\n", __func__); return; } ret = wdsp_init_and_dload_code_sections(wdsp); if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "dload code sections failed, err = %d", ret); } static int wdsp_enable_dsp(struct wdsp_mgr_priv *wdsp) { int ret; /* Make sure wdsp is in good state */ if (!WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_CODE_DLOADED)) { WDSP_ERR(wdsp, "WDSP in invalid state 0x%x", wdsp->status); ret = -EINVAL; goto done; } /* Download the read-write sections of image */ ret = wdsp_download_segments(wdsp, WDSP_ELF_FLAG_WRITE); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Data section download failed, err = %d", ret); goto done; } wdsp_broadcast_event_upseq(wdsp, WDSP_EVENT_PRE_BOOTUP, NULL); ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL, WDSP_EVENT_DO_BOOT, NULL); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Failed to boot dsp, err = %d", ret); WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED); goto done; } wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_BOOTUP, NULL); WDSP_SET_STATUS(wdsp, WDSP_STATUS_BOOTED); done: return ret; } static int wdsp_disable_dsp(struct wdsp_mgr_priv *wdsp) { int ret; WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex); /* * If Disable happened while SSR is in progress, then set the SSR * ready status indicating WDSP is now ready. Ignore the disable * event here and let the SSR handler go through shutdown. */ if (wdsp->ssr_type != WDSP_SSR_TYPE_NO_SSR) { __wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY, true); WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex); return 0; } WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex); /* Make sure wdsp is in good state */ if (!WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) { WDSP_ERR(wdsp, "wdsp in invalid state 0x%x", wdsp->status); ret = -EINVAL; goto done; } wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN, NULL); ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL, WDSP_EVENT_DO_SHUTDOWN, NULL); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Failed to shutdown dsp, err = %d", ret); goto done; } wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_SHUTDOWN, NULL); WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_BOOTED); /* Data sections are to be downloaded per boot */ WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED); done: return ret; } static int wdsp_register_cmpnt_ops(struct device *wdsp_dev, struct device *cdev, void *priv_data, struct wdsp_cmpnt_ops *ops) { struct wdsp_mgr_priv *wdsp; struct wdsp_cmpnt *cmpnt; int i, ret; if (!wdsp_dev || !cdev || !ops) return -EINVAL; wdsp = dev_get_drvdata(wdsp_dev); WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex); for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) { cmpnt = WDSP_GET_COMPONENT(wdsp, i); if ((cdev->of_node && cdev->of_node == cmpnt->np) || (cmpnt->cdev_name && !strcmp(dev_name(cdev), cmpnt->cdev_name))) { break; } } if (i == WDSP_CMPNT_TYPE_MAX) { WDSP_ERR(wdsp, "Failed to register component dev %s", dev_name(cdev)); ret = -EINVAL; goto done; } cmpnt->cdev = cdev; cmpnt->ops = ops; cmpnt->priv_data = priv_data; done: WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex); return 0; } static struct device *wdsp_get_dev_for_cmpnt(struct device *wdsp_dev, enum wdsp_cmpnt_type type) { struct wdsp_mgr_priv *wdsp; struct wdsp_cmpnt *cmpnt; if (!wdsp_dev || type >= WDSP_CMPNT_TYPE_MAX) return NULL; wdsp = dev_get_drvdata(wdsp_dev); cmpnt = WDSP_GET_COMPONENT(wdsp, type); return cmpnt->cdev; } static void wdsp_collect_ramdumps(struct wdsp_mgr_priv *wdsp) { struct wdsp_img_section img_section; struct wdsp_err_signal_arg *data = &wdsp->dump_data.err_data; struct ramdump_segment rd_seg; int ret = 0; if (wdsp->ssr_type != WDSP_SSR_TYPE_WDSP_DOWN || !data->mem_dumps_enabled) { WDSP_DBG(wdsp, "cannot dump memory, ssr_type %s, dumps %s", wdsp_get_ssr_type_string(wdsp->ssr_type), !(data->mem_dumps_enabled) ? "disabled" : "enabled"); goto done; } if (data->dump_size == 0 || data->remote_start_addr < wdsp->base_addr) { WDSP_ERR(wdsp, "Invalid start addr 0x%x or dump_size 0x%zx", data->remote_start_addr, data->dump_size); goto done; } if (!wdsp->dump_data.rd_dev) { WDSP_ERR(wdsp, "Ramdump device is not setup"); goto done; } WDSP_DBG(wdsp, "base_addr 0x%x, dump_start_addr 0x%x, dump_size 0x%zx", wdsp->base_addr, data->remote_start_addr, data->dump_size); /* Allocate memory for dumps */ wdsp->dump_data.rd_v_addr = dma_alloc_coherent(wdsp->mdev, data->dump_size, &wdsp->dump_data.rd_addr, GFP_KERNEL); if (!wdsp->dump_data.rd_v_addr) goto done; img_section.addr = data->remote_start_addr - wdsp->base_addr; img_section.size = data->dump_size; img_section.data = wdsp->dump_data.rd_v_addr; ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_TRANSPORT, WDSP_EVENT_READ_SECTION, &img_section); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Failed to read dumps, size 0x%zx at addr 0x%x", img_section.size, img_section.addr); goto err_read_dumps; } /* * If panic_on_error flag is explicitly set through the debugfs, * then cause a BUG here to aid debugging. */ BUG_ON(wdsp->panic_on_error); rd_seg.address = (unsigned long) wdsp->dump_data.rd_v_addr; rd_seg.size = img_section.size; rd_seg.v_address = wdsp->dump_data.rd_v_addr; ret = do_ramdump(wdsp->dump_data.rd_dev, &rd_seg, 1); if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "do_ramdump failed with error %d", ret); err_read_dumps: dma_free_coherent(wdsp->mdev, data->dump_size, wdsp->dump_data.rd_v_addr, wdsp->dump_data.rd_addr); done: return; } static void wdsp_ssr_work_fn(struct work_struct *work) { struct wdsp_mgr_priv *wdsp; int ret; wdsp = container_of(work, struct wdsp_mgr_priv, ssr_work); if (!wdsp) { pr_err("%s: Invalid private_data\n", __func__); return; } WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex); /* Issue ramdumps and shutdown only if DSP is currently booted */ if (WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) { wdsp_collect_ramdumps(wdsp); ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL, WDSP_EVENT_DO_SHUTDOWN, NULL); if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "Failed WDSP shutdown, err = %d", ret); wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_SHUTDOWN, NULL); WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_BOOTED); } WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex); ret = wait_for_completion_timeout(&wdsp->ready_compl, WDSP_SSR_READY_WAIT_TIMEOUT); WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex); if (ret == 0) { WDSP_ERR(wdsp, "wait_for_ready timed out, status = 0x%x", wdsp->ready_status); goto done; } /* Data sections are to downloaded per WDSP boot */ WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED); /* * Even though code section could possible be retained on DSP * crash, go ahead and still re-download just to avoid any * memory corruption from previous crash. */ WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_CODE_DLOADED); /* If codec restarted, then all components must be re-initialized */ if (wdsp->ssr_type == WDSP_SSR_TYPE_CDC_UP) { wdsp_deinit_components(wdsp); WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_INITIALIZED); } ret = wdsp_init_and_dload_code_sections(wdsp); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Failed to dload code sections err = %d", ret); goto done; } /* SSR handling is finished, mark SSR type as NO_SSR */ wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR; done: WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex); } static int wdsp_ssr_handler(struct wdsp_mgr_priv *wdsp, void *arg, enum wdsp_ssr_type ssr_type) { enum wdsp_ssr_type current_ssr_type; struct wdsp_err_signal_arg *err_data; WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex); current_ssr_type = wdsp->ssr_type; WDSP_DBG(wdsp, "Current ssr_type %s, handling ssr_type %s", wdsp_get_ssr_type_string(current_ssr_type), wdsp_get_ssr_type_string(ssr_type)); wdsp->ssr_type = ssr_type; if (arg) { err_data = (struct wdsp_err_signal_arg *) arg; memcpy(&wdsp->dump_data.err_data, err_data, sizeof(*err_data)); } else { memset(&wdsp->dump_data.err_data, 0, sizeof(wdsp->dump_data.err_data)); } switch (ssr_type) { case WDSP_SSR_TYPE_WDSP_DOWN: __wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY); wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN, NULL); schedule_work(&wdsp->ssr_work); break; case WDSP_SSR_TYPE_CDC_DOWN: __wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_CDC_READY); /* * If DSP is booted when CDC_DOWN is received, it needs * to be shutdown. */ if (WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) { __wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY); wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN, NULL); } schedule_work(&wdsp->ssr_work); break; case WDSP_SSR_TYPE_CDC_UP: __wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_CDC_READY, true); break; default: WDSP_ERR(wdsp, "undefined ssr_type %d\n", ssr_type); /* Revert back the ssr_type for undefined events */ wdsp->ssr_type = current_ssr_type; break; } WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex); return 0; } static int wdsp_signal_handler(struct device *wdsp_dev, enum wdsp_signal signal, void *arg) { struct wdsp_mgr_priv *wdsp; int ret; if (!wdsp_dev) return -EINVAL; wdsp = dev_get_drvdata(wdsp_dev); WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex); WDSP_DBG(wdsp, "Raised signal %d", signal); switch (signal) { case WDSP_IPC1_INTR: ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_IPC, WDSP_EVENT_IPC1_INTR, NULL); break; case WDSP_ERR_INTR: ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_WDSP_DOWN); break; case WDSP_CDC_DOWN_SIGNAL: ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_CDC_DOWN); break; case WDSP_CDC_UP_SIGNAL: ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_CDC_UP); break; default: ret = -EINVAL; break; } if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "handling signal %d failed with error %d", signal, ret); WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex); return ret; } static int wdsp_vote_for_dsp(struct device *wdsp_dev, bool vote) { struct wdsp_mgr_priv *wdsp; int ret = 0; if (!wdsp_dev) return -EINVAL; wdsp = dev_get_drvdata(wdsp_dev); WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex); WDSP_DBG(wdsp, "request %s, current users = %d", vote ? "enable" : "disable", wdsp->dsp_users); if (vote) { wdsp->dsp_users++; if (wdsp->dsp_users == 1) ret = wdsp_enable_dsp(wdsp); } else { if (wdsp->dsp_users == 0) goto done; wdsp->dsp_users--; if (wdsp->dsp_users == 0) ret = wdsp_disable_dsp(wdsp); } if (IS_ERR_VALUE(ret)) WDSP_DBG(wdsp, "wdsp %s failed, err = %d", vote ? "enable" : "disable", ret); done: WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex); return ret; } static int wdsp_suspend(struct device *wdsp_dev) { struct wdsp_mgr_priv *wdsp; int rc = 0, i; if (!wdsp_dev) { pr_err("%s: Invalid handle to device\n", __func__); return -EINVAL; } wdsp = dev_get_drvdata(wdsp_dev); for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) { rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_SUSPEND, NULL); if (rc < 0) { WDSP_ERR(wdsp, "component %s failed to suspend\n", WDSP_GET_CMPNT_TYPE_STR(i)); break; } } return rc; } static int wdsp_resume(struct device *wdsp_dev) { struct wdsp_mgr_priv *wdsp; int rc = 0, i; if (!wdsp_dev) { pr_err("%s: Invalid handle to device\n", __func__); return -EINVAL; } wdsp = dev_get_drvdata(wdsp_dev); for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) { rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_RESUME, NULL); if (rc < 0) { WDSP_ERR(wdsp, "component %s failed to resume\n", WDSP_GET_CMPNT_TYPE_STR(i)); break; } } return rc; } static struct wdsp_mgr_ops wdsp_ops = { .register_cmpnt_ops = wdsp_register_cmpnt_ops, .get_dev_for_cmpnt = wdsp_get_dev_for_cmpnt, .signal_handler = wdsp_signal_handler, .vote_for_dsp = wdsp_vote_for_dsp, .suspend = wdsp_suspend, .resume = wdsp_resume, }; static int wdsp_mgr_compare_of(struct device *dev, void *data) { struct wdsp_cmpnt *cmpnt = data; /* * First try to match based on of_node, if of_node is not * present, try to match on the dev_name */ return ((dev->of_node && dev->of_node == cmpnt->np) || (cmpnt->cdev_name && !strcmp(dev_name(dev), cmpnt->cdev_name))); } static void wdsp_mgr_debugfs_init(struct wdsp_mgr_priv *wdsp) { wdsp->entry = debugfs_create_dir("wdsp_mgr", NULL); if (IS_ERR_OR_NULL(wdsp->entry)) return; debugfs_create_bool("panic_on_error", S_IRUGO | S_IWUSR, wdsp->entry, &wdsp->panic_on_error); } static void wdsp_mgr_debugfs_remove(struct wdsp_mgr_priv *wdsp) { debugfs_remove_recursive(wdsp->entry); wdsp->entry = NULL; } static int wdsp_mgr_bind(struct device *dev) { struct wdsp_mgr_priv *wdsp = dev_get_drvdata(dev); struct wdsp_cmpnt *cmpnt; int ret, idx; wdsp->ops = &wdsp_ops; /* Setup ramdump device */ wdsp->dump_data.rd_dev = create_ramdump_device("wdsp", dev); if (!wdsp->dump_data.rd_dev) dev_info(dev, "%s: create_ramdump_device failed\n", __func__); ret = component_bind_all(dev, wdsp->ops); if (IS_ERR_VALUE(ret)) WDSP_ERR(wdsp, "component_bind_all failed %d\n", ret); /* Make sure all components registered ops */ for (idx = 0; idx < WDSP_CMPNT_TYPE_MAX; idx++) { cmpnt = WDSP_GET_COMPONENT(wdsp, idx); if (!cmpnt->cdev || !cmpnt->ops) { WDSP_ERR(wdsp, "%s did not register ops\n", WDSP_GET_CMPNT_TYPE_STR(idx)); ret = -EINVAL; component_unbind_all(dev, wdsp->ops); break; } } wdsp_mgr_debugfs_init(wdsp); /* Schedule the work to download image if binding was successful. */ if (!ret) schedule_work(&wdsp->load_fw_work); return ret; } static void wdsp_mgr_unbind(struct device *dev) { struct wdsp_mgr_priv *wdsp = dev_get_drvdata(dev); struct wdsp_cmpnt *cmpnt; int idx; component_unbind_all(dev, wdsp->ops); wdsp_mgr_debugfs_remove(wdsp); if (wdsp->dump_data.rd_dev) { destroy_ramdump_device(wdsp->dump_data.rd_dev); wdsp->dump_data.rd_dev = NULL; } /* Clear all status bits */ wdsp->status = 0x00; /* clean up the components */ for (idx = 0; idx < WDSP_CMPNT_TYPE_MAX; idx++) { cmpnt = WDSP_GET_COMPONENT(wdsp, idx); cmpnt->cdev = NULL; cmpnt->ops = NULL; cmpnt->priv_data = NULL; } } static const struct component_master_ops wdsp_master_ops = { .bind = wdsp_mgr_bind, .unbind = wdsp_mgr_unbind, }; static void *wdsp_mgr_parse_phandle(struct wdsp_mgr_priv *wdsp, int index) { struct device *mdev = wdsp->mdev; struct device_node *np; struct wdsp_cmpnt *cmpnt = NULL; struct of_phandle_args pargs; u32 value; int ret; ret = of_parse_phandle_with_fixed_args(mdev->of_node, "qcom,wdsp-components", 1, index, &pargs); if (ret) { WDSP_ERR(wdsp, "parse_phandle at index %d failed %d", index, ret); return NULL; } np = pargs.np; value = pargs.args[0]; if (value >= WDSP_CMPNT_TYPE_MAX) { WDSP_ERR(wdsp, "invalid phandle_arg to of_node %s", np->name); goto done; } cmpnt = WDSP_GET_COMPONENT(wdsp, value); if (cmpnt->np || cmpnt->cdev_name) { WDSP_ERR(wdsp, "cmpnt %d already added", value); cmpnt = NULL; goto done; } cmpnt->np = np; of_property_read_string(np, "qcom,wdsp-cmpnt-dev-name", &cmpnt->cdev_name); done: of_node_put(np); return cmpnt; } static int wdsp_mgr_parse_dt_entries(struct wdsp_mgr_priv *wdsp) { struct device *dev = wdsp->mdev; void *match_data; int ph_idx, ret; ret = of_property_read_string(dev->of_node, "qcom,img-filename", &wdsp->img_fname); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Reading property %s failed, error = %d", "qcom,img-filename", ret); return ret; } ret = of_count_phandle_with_args(dev->of_node, "qcom,wdsp-components", NULL); if (ret == -ENOENT) { WDSP_ERR(wdsp, "Property %s not defined in DT", "qcom,wdsp-components"); goto done; } else if (ret != WDSP_CMPNT_TYPE_MAX * 2) { WDSP_ERR(wdsp, "Invalid phandle + arg count %d, expected %d", ret, WDSP_CMPNT_TYPE_MAX * 2); ret = -EINVAL; goto done; } ret = 0; for (ph_idx = 0; ph_idx < WDSP_CMPNT_TYPE_MAX; ph_idx++) { match_data = wdsp_mgr_parse_phandle(wdsp, ph_idx); if (!match_data) { WDSP_ERR(wdsp, "component not found at idx %d", ph_idx); ret = -EINVAL; goto done; } component_match_add(dev, &wdsp->match, wdsp_mgr_compare_of, match_data); } done: return ret; } static int wdsp_mgr_probe(struct platform_device *pdev) { struct wdsp_mgr_priv *wdsp; struct device *mdev = &pdev->dev; int ret; wdsp = devm_kzalloc(mdev, sizeof(*wdsp), GFP_KERNEL); if (!wdsp) return -ENOMEM; wdsp->mdev = mdev; wdsp->seg_list = devm_kzalloc(mdev, sizeof(struct list_head), GFP_KERNEL); if (!wdsp->seg_list) { devm_kfree(mdev, wdsp); return -ENOMEM; } ret = wdsp_mgr_parse_dt_entries(wdsp); if (ret) goto err_dt_parse; INIT_WORK(&wdsp->load_fw_work, wdsp_load_fw_image); INIT_LIST_HEAD(wdsp->seg_list); mutex_init(&wdsp->api_mutex); mutex_init(&wdsp->ssr_mutex); wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR; wdsp->ready_status = WDSP_SSR_STATUS_READY; INIT_WORK(&wdsp->ssr_work, wdsp_ssr_work_fn); init_completion(&wdsp->ready_compl); arch_setup_dma_ops(wdsp->mdev, 0, 0, NULL, 0); dev_set_drvdata(mdev, wdsp); ret = component_master_add_with_match(mdev, &wdsp_master_ops, wdsp->match); if (IS_ERR_VALUE(ret)) { WDSP_ERR(wdsp, "Failed to add master, err = %d", ret); goto err_master_add; } return 0; err_master_add: mutex_destroy(&wdsp->api_mutex); mutex_destroy(&wdsp->ssr_mutex); err_dt_parse: devm_kfree(mdev, wdsp->seg_list); devm_kfree(mdev, wdsp); dev_set_drvdata(mdev, NULL); return ret; } static int wdsp_mgr_remove(struct platform_device *pdev) { struct device *mdev = &pdev->dev; struct wdsp_mgr_priv *wdsp = dev_get_drvdata(mdev); component_master_del(mdev, &wdsp_master_ops); mutex_destroy(&wdsp->api_mutex); mutex_destroy(&wdsp->ssr_mutex); devm_kfree(mdev, wdsp->seg_list); devm_kfree(mdev, wdsp); dev_set_drvdata(mdev, NULL); return 0; }; static const struct of_device_id wdsp_mgr_dt_match[] = { {.compatible = "qcom,wcd-dsp-mgr" }, { } }; static struct platform_driver wdsp_mgr_driver = { .driver = { .name = "wcd-dsp-mgr", .owner = THIS_MODULE, .of_match_table = of_match_ptr(wdsp_mgr_dt_match), }, .probe = wdsp_mgr_probe, .remove = wdsp_mgr_remove, }; module_platform_driver(wdsp_mgr_driver); MODULE_DESCRIPTION("WCD DSP manager driver"); MODULE_DEVICE_TABLE(of, wdsp_mgr_dt_match); MODULE_LICENSE("GPL v2");