// SPDX-License-Identifier: GPL-2.0-only /* * Google Whitechapel AoC Core Driver * * Copyright (c) 2019-2021 Google LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(fmt) "aoc: " fmt #include "aoc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aoc_firmware.h" #include "aoc_ramdump_regions.h" #define AOC_MAX_MINOR (1U) #define AOC_FWDATA_ENTRIES 10 #define AOC_FWDATA_BOARDID_DFL 0x20202 #define AOC_FWDATA_BOARDREV_DFL 0x10000 #define MAX_RESET_REASON_STRING_LEN 128UL #define RESET_WAIT_TIMES_NUM 3 #define RESET_WAIT_TIME_MS 3000 #define RESET_WAIT_TIME_INCREMENT_MS 2048 DEFINE_MUTEX(aoc_service_lock); enum AOC_FW_STATE aoc_state; struct platform_device *aoc_platform_device; /* TODO: Reduce the global variables (move into a driver structure) */ /* Resources found from the device tree */ struct resource *aoc_sram_resource; struct sscd_info { char *name; struct sscd_segment segs[256]; u16 seg_count; }; static void sscd_release(struct device *dev); static struct sscd_info sscd_info; static struct sscd_platform_data sscd_pdata; static struct platform_device sscd_dev = { .name = "aoc", .driver_override = SSCD_NAME, .id = -1, .dev = { .platform_data = &sscd_pdata, .release = sscd_release, } }; static void *aoc_sram_virt_mapping; static void *aoc_dram_virt_mapping; static int aoc_irq; struct aoc_control_block *aoc_control; static int aoc_major; static const char *default_firmware = "aoc.bin"; static bool aoc_autoload_firmware = false; module_param(aoc_autoload_firmware, bool, 0644); MODULE_PARM_DESC(aoc_autoload_firmware, "Automatically load firmware if true"); static bool aoc_disable_restart = false; module_param(aoc_disable_restart, bool, 0644); MODULE_PARM_DESC(aoc_disable_restart, "Prevent AoC from restarting after crashing."); static bool aoc_panic_on_req_timeout = true; module_param(aoc_panic_on_req_timeout, bool, 0644); MODULE_PARM_DESC(aoc_panic_on_req_timeout, "Enable kernel panic when aoc_req times out."); static struct aoc_module_parameters aoc_module_params = { .aoc_autoload_firmware = &aoc_autoload_firmware, .aoc_disable_restart = &aoc_disable_restart, .aoc_panic_on_req_timeout = &aoc_panic_on_req_timeout, }; static int aoc_core_suspend(struct device *dev); static int aoc_core_resume(struct device *dev); const static struct dev_pm_ops aoc_core_pm_ops = { .suspend = aoc_core_suspend, .resume = aoc_core_resume, }; static int aoc_bus_match(struct device *dev, struct device_driver *drv); static int aoc_bus_probe(struct device *dev); static void aoc_bus_remove(struct device *dev); static void aoc_configure_sysmmu_fault_handler(struct aoc_prvdata *p); static void aoc_configure_sysmmu(struct aoc_prvdata *p, const struct firmware *fw); static struct bus_type aoc_bus_type = { .name = "aoc", .match = aoc_bus_match, .probe = aoc_bus_probe, .remove = aoc_bus_remove, }; struct aoc_client { int client_id; int endpoint; }; static bool write_reset_trampoline(const struct firmware *fw); static bool configure_dmic_regulator(struct aoc_prvdata *prvdata, bool enable); static bool configure_sensor_regulator(struct aoc_prvdata *prvdata, bool enable); static void aoc_take_offline(struct aoc_prvdata *prvdata); static void aoc_process_services(struct aoc_prvdata *prvdata, int offset); static void aoc_watchdog(struct work_struct *work); void *aoc_sram_translate(u32 offset) { BUG_ON(aoc_sram_virt_mapping == NULL); if (offset > resource_size(aoc_sram_resource)) return NULL; return aoc_sram_virt_mapping + offset; } static inline void *aoc_dram_translate(struct aoc_prvdata *p, u32 offset) { BUG_ON(p->dram_virt == NULL); if (offset > p->dram_size) return NULL; return p->dram_virt + offset; } bool aoc_is_valid_dram_address(struct aoc_prvdata *prv, void *addr) { ptrdiff_t offset; if (addr < prv->dram_virt) return false; offset = addr - prv->dram_virt; return (offset < prv->dram_size); } phys_addr_t aoc_dram_translate_to_aoc(struct aoc_prvdata *p, phys_addr_t addr) { phys_addr_t phys_start = p->dram_resource.start; phys_addr_t phys_end = phys_start + resource_size(&p->dram_resource); u32 offset; if (addr < phys_start || addr >= phys_end) return 0; offset = addr - phys_start; return AOC_BINARY_DRAM_BASE + offset; } bool aoc_fw_ready(void) { return aoc_control != NULL && aoc_control->magic == AOC_MAGIC; } static int driver_matches_service_by_name(struct device_driver *drv, void *name) { struct aoc_driver *aoc_drv = AOC_DRIVER(drv); const char *service_name = name; const char *const *driver_names = aoc_drv->service_names; while (driver_names && *driver_names) { if (glob_match(*driver_names, service_name) == true) return 1; driver_names++; } return 0; } static bool has_name_matching_driver(const char *service_name) { return bus_for_each_drv(&aoc_bus_type, NULL, (char *)service_name, driver_matches_service_by_name) != 0; } static bool service_names_are_valid(struct aoc_prvdata *prv) { int services, i, j; const char *name; services = aoc_num_services(); if (services == 0) return false; /* All names have a valid length */ for (i = 0; i < services; i++) { size_t name_len; name = aoc_service_name(service_at_index(prv, i)); if (!name) { dev_err(prv->dev, "failed to retrieve service name for service %d\n", i); return false; } name_len = strnlen(name, AOC_SERVICE_NAME_LENGTH); if (name_len == 0 || name_len == AOC_SERVICE_NAME_LENGTH) { dev_err(prv->dev, "service %d has a name that is too long\n", i); return false; } dev_dbg(prv->dev, "validated service %d name %s\n", i, name); } /* No duplicate names */ for (i = 0; i < services; i++) { char name1[AOC_SERVICE_NAME_LENGTH], name2[AOC_SERVICE_NAME_LENGTH]; name = aoc_service_name(service_at_index(prv, i)); if (!name) { dev_err(prv->dev, "failed to retrieve service name for service %d\n", i); return false; } memcpy_fromio(name1, name, sizeof(name1)); for (j = i + 1; j < services; j++) { name = aoc_service_name(service_at_index(prv, j)); if (!name) { dev_err(prv->dev, "failed to retrieve service name for service %d\n", j); return false; } memcpy_fromio(name2, name, sizeof(name2)); if (strncmp(name1, name2, AOC_SERVICE_NAME_LENGTH) == 0) { dev_err(prv->dev, "service %d and service %d have the same name\n", i, j); return false; } } } return true; } static void free_mailbox_channels(struct aoc_prvdata *prv); static int allocate_mailbox_channels(struct aoc_prvdata *prv) { struct device *dev = prv->dev; struct mbox_slot *slot; int i, rc = 0; for (i = 0; i < prv->aoc_mbox_channels; i++) { slot = &prv->mbox_channels[i]; slot->channel = mbox_request_channel(&slot->client, i); if (IS_ERR(slot->channel)) { rc = PTR_ERR(slot->channel); dev_err(dev, "failed to find mailbox interface %d : %d\n", i, rc); slot->channel = NULL; goto err_mbox_req; } } err_mbox_req: if (rc != 0) free_mailbox_channels(prv); return rc; } static void free_mailbox_channels(struct aoc_prvdata *prv) { struct mbox_slot *slot; int i; for (i = 0; i < prv->aoc_mbox_channels; i++) { slot = &prv->mbox_channels[i]; if (slot->channel) { mbox_free_channel(slot->channel); slot->channel = NULL; } } } static void aoc_mbox_rx_callback(struct mbox_client *cl, void *mssg) { struct mbox_slot *slot = container_of(cl, struct mbox_slot, client); struct aoc_prvdata *prvdata = slot->prvdata; switch (aoc_state) { case AOC_STATE_FIRMWARE_LOADED: if (aoc_fw_ready()) { aoc_state = AOC_STATE_STARTING; schedule_work(&prvdata->online_work); } break; case AOC_STATE_ONLINE: aoc_process_services(prvdata, slot->index); break; default: break; } } static void aoc_mbox_tx_prepare(struct mbox_client *cl, void *mssg) { } static void aoc_mbox_tx_done(struct mbox_client *cl, void *mssg, int r) { } extern int gs_chipid_get_ap_hw_tune_array(const u8 **array); static inline bool aoc_sram_was_repaired(struct aoc_prvdata *prvdata) { return false; } struct aoc_fw_data { u32 key; u32 value; }; u32 dt_property(struct device_node *node, const char *key) { u32 ret; if (of_property_read_u32(node, key, &ret)) return DT_PROPERTY_NOT_FOUND; return ret; } EXPORT_SYMBOL_GPL(dt_property); static void aoc_pass_fw_information(void *base, const struct aoc_fw_data *fwd, size_t num) { u32 *data = base; int i; writel_relaxed(AOC_PARAMETER_MAGIC, data++); writel_relaxed(num, data++); writel_relaxed(12 + (num * (3 * sizeof(u32))), data++); for (i = 0; i < num; i++) { writel_relaxed(fwd[i].key, data++); writel_relaxed(sizeof(u32), data++); writel_relaxed(fwd[i].value, data++); } } static u32 aoc_board_config_parse(struct device_node *node, u32 *board_id, u32 *board_rev) { const char *board_cfg; int err = 0; /* Read board config from device tree */ err = of_property_read_string(node, "aoc-board-cfg", &board_cfg); if (err < 0) { pr_err("Unable to retrieve AoC board configuration, check DT"); pr_info("Assuming R4/O6 board configuration"); *board_id = AOC_FWDATA_BOARDID_DFL; *board_rev = AOC_FWDATA_BOARDREV_DFL; return err; } /* Read board id from device tree */ err = of_property_read_u32(node, "aoc-board-id", board_id); if (err < 0) { pr_err("Unable to retrieve AoC board id, check DT"); pr_info("Assuming R4/O6 board configuration"); *board_id = AOC_FWDATA_BOARDID_DFL; *board_rev = AOC_FWDATA_BOARDREV_DFL; return err; } /* Read board revision from device tree */ err = of_property_read_u32(node, "aoc-board-rev", board_rev); if (err < 0) { pr_err("Unable to retrieve AoC board revision, check DT"); pr_info("Assuming R4/O6 board configuration"); *board_id = AOC_FWDATA_BOARDID_DFL; *board_rev = AOC_FWDATA_BOARDREV_DFL; return err; } pr_info("AoC Platform: %s", board_cfg); return err; } static int aoc_fw_authenticate(struct aoc_prvdata *prvdata, const struct firmware *fw) { int rc; dma_addr_t header_dma_addr; void *header_vaddr; /* Allocate coherent memory for the image header */ header_vaddr = dma_alloc_coherent(prvdata->gsa_dev, AOC_AUTH_HEADER_SIZE, &header_dma_addr, GFP_KERNEL); if (!header_vaddr) { dev_err(prvdata->dev, "Failed to allocate coherent memory for header\n"); rc = -ENOMEM; goto err_alloc; } memcpy(header_vaddr, fw->data, AOC_AUTH_HEADER_SIZE); rc = gsa_load_aoc_fw_image(prvdata->gsa_dev, header_dma_addr, prvdata->dram_resource.start + AOC_BINARY_DRAM_OFFSET); if (rc) { dev_err(prvdata->dev, "GSA authentication failed: %d\n", rc); goto err_auth; } err_auth: err_alloc: dma_free_coherent(prvdata->gsa_dev, AOC_AUTH_HEADER_SIZE, header_vaddr, header_dma_addr); return rc; } static void aoc_fw_callback(const struct firmware *fw, void *ctx) { static bool first_load_prevented = false; struct device *dev = ctx; struct aoc_prvdata *prvdata = dev_get_drvdata(dev); u32 sram_was_repaired = aoc_sram_was_repaired(prvdata); u32 carveout_base = prvdata->dram_resource.start; u32 carveout_size = prvdata->dram_size; u32 dt_force_vnom = dt_property(prvdata->dev->of_node, "force-vnom"); u32 force_vnom = ((dt_force_vnom != 0) || (prvdata->force_voltage_nominal != 0)) ? 1 : 0; u32 disable_mm = prvdata->disable_monitor_mode; u32 enable_uart = prvdata->enable_uart_tx; u32 force_speaker_ultrasonic = prvdata->force_speaker_ultrasonic; u32 board_id = AOC_FWDATA_BOARDID_DFL; u32 board_rev = AOC_FWDATA_BOARDREV_DFL; u32 rand_seed = get_random_u32(); u32 chip_revision = gs_chipid_get_revision(); u32 chip_type = gs_chipid_get_type(); u32 dt_gnss_type = dt_property(prvdata->dev->of_node, "gnss-type"); u32 gnss_type = dt_gnss_type == 0xffffffff ? 0 : dt_gnss_type; bool dt_prevent_aoc_load = (dt_property(prvdata->dev->of_node, "prevent-fw-load")==1); phys_addr_t sensor_heap = aoc_dram_translate_to_aoc(prvdata, prvdata->sensor_heap_base); phys_addr_t playback_heap = aoc_dram_translate_to_aoc(prvdata, prvdata->audio_playback_heap_base); phys_addr_t capture_heap = aoc_dram_translate_to_aoc(prvdata, prvdata->audio_capture_heap_base); unsigned int i; bool fw_signed, gsa_enabled; struct aoc_fw_data fw_data[] = { { .key = kAOCBoardID, .value = board_id }, { .key = kAOCBoardRevision, .value = board_rev }, { .key = kAOCSRAMRepaired, .value = sram_was_repaired }, { .key = kAOCCarveoutAddress, .value = carveout_base}, { .key = kAOCCarveoutSize, .value = carveout_size}, { .key = kAOCSensorDirectHeapAddress, .value = sensor_heap}, { .key = kAOCSensorDirectHeapSize, .value = SENSOR_DIRECT_HEAP_SIZE }, { .key = kAOCPlaybackHeapAddress, .value = playback_heap}, { .key = kAOCPlaybackHeapSize, .value = PLAYBACK_HEAP_SIZE }, { .key = kAOCCaptureHeapAddress, .value = capture_heap}, { .key = kAOCCaptureHeapSize, .value = CAPTURE_HEAP_SIZE }, { .key = kAOCForceVNOM, .value = force_vnom }, { .key = kAOCDisableMM, .value = disable_mm }, { .key = kAOCEnableUART, .value = enable_uart }, { .key = kAOCForceSpeakerUltrasonic, .value = force_speaker_ultrasonic }, { .key = kAOCRandSeed, .value = rand_seed }, { .key = kAOCChipRevision, .value = chip_revision }, { .key = kAOCChipType, .value = chip_type }, { .key = kAOCGnssType, .value = gnss_type } }; const char *version; u32 fw_data_entries = ARRAY_SIZE(fw_data); u32 ipc_offset; if ((dt_prevent_aoc_load) && (!first_load_prevented)) { dev_err(dev, "DTS settings prevented AoC firmware from being loaded\n"); first_load_prevented = true; return; } aoc_board_config_parse(prvdata->dev->of_node, &board_id, &board_rev); if (!fw) { dev_err(dev, "Failed to load AoC firmware image\n"); return; } if (prvdata->force_release_aoc) { dev_info(dev, "Force Reload Trigger: Free current loaded\n"); goto free_fw; } for (i = 0; i < fw_data_entries; i++) { if (fw_data[i].key == kAOCBoardID) fw_data[i].value = board_id; else if (fw_data[i].key == kAOCBoardRevision) fw_data[i].value = board_rev; } request_aoc_on(prvdata, true); if (!fw->data) { dev_err(dev, "firmware image contains no data\n"); goto free_fw; } if (!_aoc_fw_is_valid(fw)) { dev_err(dev, "firmware validation failed\n"); goto free_fw; } ipc_offset = _aoc_fw_ipc_offset(fw); version = _aoc_fw_version(fw); prvdata->firmware_version = devm_kasprintf(dev, GFP_KERNEL, "%s", version); pr_notice("successfully loaded firmware version %s type %s", version ? version : "unknown", _aoc_fw_is_release(fw) ? "release" : "development"); if (prvdata->disable_monitor_mode) dev_err(dev, "Monitor Mode will be disabled. Power will be impacted\n"); if (prvdata->enable_uart_tx) dev_err(dev, "Enabling logging on UART. This will affect system timing\n"); if (prvdata->force_voltage_nominal) dev_err(dev, "Forcing VDD_AOC to VNOM on this device. Power will be impacted\n"); else dev_info(dev, "AoC using default DVFS on this device.\n"); if (prvdata->no_ap_resets) dev_err(dev, "Resets by AP via sysfs are disabled\n"); if (prvdata->force_speaker_ultrasonic) dev_err(dev, "Forcefully enabling Speaker Ultrasonic pipeline\n"); if (!_aoc_fw_is_compatible(fw)) { dev_err(dev, "firmware and drivers are incompatible\n"); goto free_fw; } fw_signed = _aoc_fw_is_signed(fw); if (!fw_signed) { dev_err(dev, "Loading unsigned aoc image is unsupported\n"); goto free_fw; } dev_info(dev, "Loading signed aoc image\n"); prvdata->protected_by_gsa = fw_signed; aoc_control = aoc_dram_translate(prvdata, ipc_offset); { bool commit_rc = _aoc_fw_commit(fw, aoc_dram_virt_mapping + AOC_BINARY_DRAM_OFFSET); if (!commit_rc) { dev_err(dev, "FW commit failed!\n"); } } gsa_enabled = of_property_read_bool(prvdata->dev->of_node, "gsa-enabled"); if (gsa_enabled) { int rc; aoc_configure_sysmmu_fault_handler(prvdata); rc = aoc_fw_authenticate(prvdata, fw); if (rc) { dev_err(dev, "GSA: FW authentication failed: %d\n", rc); goto free_fw; } } else { aoc_configure_sysmmu(prvdata, fw); write_reset_trampoline(fw); } aoc_pass_fw_information(aoc_dram_translate(prvdata, ipc_offset), fw_data, ARRAY_SIZE(fw_data)); aoc_state = AOC_STATE_FIRMWARE_LOADED; /* AOC needs DRAM while booting, so prevent AP from sleep. */ dev_info(dev, "preventing AP from sleep for 2 sec for aoc boot\n"); disable_power_mode(0, POWERMODE_TYPE_SYSTEM); prvdata->ipc_base = aoc_dram_translate(prvdata, ipc_offset); /* start AOC */ if (gsa_enabled) { int rc = gsa_send_aoc_cmd(prvdata->gsa_dev, GSA_AOC_START); if (rc < 0) { dev_err(dev, "GSA: Failed to start AOC: %d\n", rc); goto free_fw; } } else { aoc_release_from_reset(prvdata); } configure_crash_interrupts(prvdata, true); /* Monitor if there is callback from aoc after 5sec */ cancel_delayed_work_sync(&prvdata->monitor_work); schedule_delayed_work(&prvdata->monitor_work, msecs_to_jiffies(5 * 1000)); msleep(2000); dev_info(dev, "re-enabling low power mode\n"); enable_power_mode(0, POWERMODE_TYPE_SYSTEM); release_firmware(fw); return; free_fw: /* Change aoc_state to offline due to abnormal firmware */ aoc_state = AOC_STATE_OFFLINE; release_firmware(fw); } phys_addr_t aoc_service_ring_base_phys_addr(struct aoc_service_dev *dev, aoc_direction dir, size_t *out_size) { const struct device *parent; struct aoc_prvdata *prvdata; aoc_service *service; void *ring_base; if (!dev) return -EINVAL; parent = dev->dev.parent; prvdata = dev_get_drvdata(parent); service = service_at_index(prvdata, dev->service_index); ring_base = aoc_service_ring_base(service, prvdata->ipc_base, dir); pr_debug("aoc DRAM starts at (virt): %pK, (phys):%llx, ring base (virt): %pK", aoc_dram_virt_mapping, prvdata->dram_resource.start, ring_base); if (out_size) *out_size = aoc_service_ring_size(service, dir); return ring_base - aoc_dram_virt_mapping + prvdata->dram_resource.start; } EXPORT_SYMBOL_GPL(aoc_service_ring_base_phys_addr); phys_addr_t aoc_get_heap_base_phys_addr(struct aoc_service_dev *dev, aoc_direction dir, size_t *out_size) { const struct device *parent; struct aoc_prvdata *prvdata; aoc_service *service; phys_addr_t audio_heap_base; if (!dev) return -EINVAL; parent = dev->dev.parent; prvdata = dev_get_drvdata(parent); service = service_at_index(prvdata, dev->service_index); if (out_size) *out_size = aoc_service_ring_size(service, dir); if (dir == AOC_DOWN) audio_heap_base = prvdata->audio_playback_heap_base; else audio_heap_base = prvdata->audio_capture_heap_base; pr_debug("Get heap address(phy):%pap\n", &audio_heap_base); return audio_heap_base; } EXPORT_SYMBOL_GPL(aoc_get_heap_base_phys_addr); static bool write_reset_trampoline(const struct firmware *fw) { u32 *reset, bl_size; u32 *bootloader; reset = aoc_sram_translate(0); if (!reset) return false; bl_size = _aoc_fw_bl_size(fw); bootloader = _aoc_fw_bl(fw); pr_notice("writing reset trampoline to addr %#x\n", bootloader[bl_size / sizeof(u32) - 1]); memcpy_toio(reset, bootloader, bl_size); return true; } static ssize_t coredump_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%u\n", prvdata->total_coredumps); } static DEVICE_ATTR_RO(coredump_count); static ssize_t restart_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%u\n", prvdata->total_restarts); } static DEVICE_ATTR_RO(restart_count); static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) { u32 fw_rev, hw_rev; if (!aoc_fw_ready()) return scnprintf(buf, PAGE_SIZE, "Offline\n"); fw_rev = le32_to_cpu(aoc_control->fw_version); hw_rev = le32_to_cpu(aoc_control->hw_version); return scnprintf(buf, PAGE_SIZE, "FW Revision : %#x\nHW Revision : %#x\n", fw_rev, hw_rev); } static DEVICE_ATTR_RO(revision); static uint64_t clock_offset(void) { u64 clock_offset; if (!aoc_fw_ready()) return 0; memcpy_fromio(&clock_offset, &aoc_control->system_clock_offset, sizeof(clock_offset)); return le64_to_cpu(clock_offset); } static inline u64 sys_tick_to_aoc_tick(u64 sys_tick) { struct aoc_prvdata *prvdata = platform_get_drvdata(aoc_platform_device); return (sys_tick - clock_offset()) / prvdata->aoc_clock_divider; } static ssize_t aoc_clock_show(struct device *dev, struct device_attribute *attr, char *buf) { u64 counter; if (!aoc_fw_ready()) return scnprintf(buf, PAGE_SIZE, "0\n"); counter = arch_timer_read_counter(); return scnprintf(buf, PAGE_SIZE, "%llu\n", sys_tick_to_aoc_tick(counter)); } static DEVICE_ATTR_RO(aoc_clock); static ssize_t aoc_clock_and_kernel_boottime_show(struct device *dev, struct device_attribute *attr, char *buf) { u64 counter; ktime_t kboottime; if (!aoc_fw_ready()) return scnprintf(buf, PAGE_SIZE, "0 0\n"); counter = arch_timer_read_counter(); kboottime = ktime_get_boottime(); return scnprintf(buf, PAGE_SIZE, "%llu %llu\n", sys_tick_to_aoc_tick(counter), (u64)kboottime); } static DEVICE_ATTR_RO(aoc_clock_and_kernel_boottime); static ssize_t clock_offset_show(struct device *dev, struct device_attribute *attr, char *buf) { if (!aoc_fw_ready()) return scnprintf(buf, PAGE_SIZE, "0\n"); return scnprintf(buf, PAGE_SIZE, "%lld\n", clock_offset()); } static DEVICE_ATTR_RO(clock_offset); static ssize_t services_show(struct device *dev, struct device_attribute *attr, char *buf) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); int services = aoc_num_services(); int ret = 0; int i; atomic_inc(&prvdata->aoc_process_active); if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) goto exit; ret += scnprintf(buf, PAGE_SIZE, "Services : %d\n", services); for (i = 0; i < services && ret < (PAGE_SIZE - 1); i++) { aoc_service *s = service_at_index(prvdata, i); struct aoc_ipc_service_header *hdr = (struct aoc_ipc_service_header *)s; ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%d : \"%s\" mbox %d\n", i, aoc_service_name(s), aoc_service_irq_index(s)); if (hdr->regions[0].slots > 0) { ret += scnprintf( buf + ret, PAGE_SIZE - ret, " Up Size:%ux%uB Tx:%u Rx:%u\n", hdr->regions[0].slots, hdr->regions[0].size, hdr->regions[0].tx, hdr->regions[0].rx); } if (hdr->regions[1].slots > 0) { ret += scnprintf( buf + ret, PAGE_SIZE - ret, " Down Size:%ux%uB Tx:%u Rx:%u\n", hdr->regions[1].slots, hdr->regions[1].size, hdr->regions[1].tx, hdr->regions[1].rx); } } exit: atomic_dec(&prvdata->aoc_process_active); return ret; } static DEVICE_ATTR_RO(services); int start_firmware_load(struct device *dev) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); dev_notice(dev, "attempting to load firmware \"%s\"\n", prvdata->firmware_name); return request_firmware_nowait(THIS_MODULE, true, prvdata->firmware_name, dev, GFP_KERNEL, dev, aoc_fw_callback); } static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, char *buf) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%s", prvdata->firmware_name); } static ssize_t firmware_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); char buffer[MAX_FIRMWARE_LENGTH]; char *trimmed = NULL; if (strscpy(buffer, buf, sizeof(buffer)) <= 0) return -E2BIG; if (strchr(buffer, '/') != NULL) { dev_err(dev, "firmware path must not contain '/'\n"); return -EINVAL; } /* Strip whitespace (including \n) */ trimmed = strim(buffer); strscpy(prvdata->firmware_name, trimmed, sizeof(prvdata->firmware_name)); start_firmware_load(dev); return count; } static DEVICE_ATTR_RW(firmware); static ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); char reason_str[MAX_RESET_REASON_STRING_LEN + 1]; if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) { dev_err(dev, "Reset requested while AoC is not online"); return -ENODEV; } strscpy(reason_str, buf, sizeof(reason_str)); dev_err(dev, "Reset requested from userspace, reason: %s", reason_str); if (prvdata->no_ap_resets) { dev_err(dev, "Reset request rejected, option disabled via persist options"); } else { configure_crash_interrupts(prvdata, false); strlcpy(prvdata->ap_reset_reason, reason_str, AP_RESET_REASON_LENGTH); prvdata->ap_triggered_reset = true; schedule_work(&prvdata->watchdog_work); } return count; } static DEVICE_ATTR_WO(reset); static ssize_t force_reload_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); /* Force release current loaded AoC if watchdog already active */ prvdata->force_release_aoc = true; while (work_busy(&prvdata->watchdog_work) || work_busy(&prvdata->monitor_work.work)); prvdata->force_release_aoc = false; /* Disable IRQ if AoC is loaded for paired IRQ */ if (aoc_state != AOC_STATE_OFFLINE) disable_irq_nosync(prvdata->watchdog_irq); strlcpy(prvdata->ap_reset_reason, "Force Reload AoC", AP_RESET_REASON_LENGTH); prvdata->ap_triggered_reset = true; schedule_work(&prvdata->watchdog_work); return count; } static DEVICE_ATTR_WO(force_reload); static ssize_t dmic_power_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); int val; if (kstrtoint(buf, 10, &val) == 0) { dev_info(prvdata->dev,"dmic_power_enable %d", val); configure_dmic_regulator(prvdata, !!val); } return count; } static DEVICE_ATTR_WO(dmic_power_enable); static ssize_t sensor_power_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); int val; if (kstrtoint(buf, 10, &val) == 0) { dev_info(prvdata->dev,"sensor_power_enable %d", val); configure_sensor_regulator(prvdata, !!val); } return count; } static DEVICE_ATTR_WO(sensor_power_enable); static ssize_t notify_timeout_aoc_status_show(struct device *dev, struct device_attribute *attr, char *buf) { return 0; } static DEVICE_ATTR_RO(notify_timeout_aoc_status); static struct attribute *aoc_attrs[] = { &dev_attr_firmware.attr, &dev_attr_revision.attr, &dev_attr_services.attr, &dev_attr_coredump_count.attr, &dev_attr_restart_count.attr, &dev_attr_clock_offset.attr, &dev_attr_aoc_clock.attr, &dev_attr_aoc_clock_and_kernel_boottime.attr, &dev_attr_reset.attr, &dev_attr_sensor_power_enable.attr, &dev_attr_force_reload.attr, &dev_attr_dmic_power_enable.attr, &dev_attr_notify_timeout_aoc_status.attr, NULL }; ATTRIBUTE_GROUPS(aoc); static int aoc_platform_probe(struct platform_device *dev); static int aoc_platform_remove(struct platform_device *dev); static void aoc_platform_shutdown(struct platform_device *dev); static const struct of_device_id aoc_match[] = { { .compatible = "google,aoc", }, {}, }; static struct platform_driver aoc_driver = { .probe = aoc_platform_probe, .remove = aoc_platform_remove, .shutdown = aoc_platform_shutdown, .driver = { .name = "aoc", .owner = THIS_MODULE, .pm = &aoc_core_pm_ops, .of_match_table = of_match_ptr(aoc_match), }, }; static int aoc_bus_match(struct device *dev, struct device_driver *drv) { struct aoc_driver *driver = AOC_DRIVER(drv); const char *device_name = dev_name(dev); bool driver_matches_by_name = (driver->service_names != NULL); pr_debug("bus match dev:%s drv:%s\n", device_name, drv->name); /* * If the driver matches by name, only call probe if the name matches. * * If there is a specific driver matching this service, do not allow a * generic driver to claim the service */ if (!driver_matches_by_name && has_name_matching_driver(device_name)) { pr_debug("ignoring generic driver for service %s\n", device_name); return 0; } /* Drivers with a name only match services with that name */ if (driver_matches_by_name && !driver_matches_service_by_name(drv, (char *)device_name)) { return 0; } return 1; } static int aoc_bus_probe(struct device *dev) { struct aoc_service_dev *the_dev = AOC_DEVICE(dev); struct aoc_driver *driver = AOC_DRIVER(dev->driver); pr_debug("bus probe dev:%s\n", dev_name(dev)); if (!driver->probe) return -ENODEV; return driver->probe(the_dev); } static void aoc_bus_remove(struct device *dev) { struct aoc_service_dev *aoc_dev = AOC_DEVICE(dev); struct aoc_driver *drv = AOC_DRIVER(dev->driver); pr_notice("bus remove %s\n", dev_name(dev)); if (drv->remove) drv->remove(aoc_dev); } int aoc_driver_register(struct aoc_driver *driver) { driver->drv.bus = &aoc_bus_type; return driver_register(&driver->drv); } EXPORT_SYMBOL_GPL(aoc_driver_register); void aoc_driver_unregister(struct aoc_driver *driver) { driver_unregister(&driver->drv); } EXPORT_SYMBOL_GPL(aoc_driver_unregister); static int aoc_wakeup_queues(struct device *dev, void *ctx) { struct aoc_service_dev *the_dev = AOC_DEVICE(dev); /* * Once dead is set to true, function calls using this AoC device will return error. * Clients may still hold a refcount on the AoC device, so freeing is delayed. */ the_dev->dead = true; // Allow any pending reads and writes to finish before removing devices wake_up(&the_dev->read_queue); wake_up(&the_dev->write_queue); return 0; } static int aoc_remove_device(struct device *dev, void *ctx) { device_unregister(dev); return 0; } /* Service devices are freed after offline is complete */ static void aoc_device_release(struct device *dev) { struct aoc_service_dev *the_dev = AOC_DEVICE(dev); pr_debug("%s %s\n", __func__, dev_name(dev)); kfree(the_dev); } static struct aoc_service_dev *create_service_device(struct aoc_prvdata *prvdata, int index) { struct device *parent = prvdata->dev; char service_name[32]; const char *name; aoc_service *s; struct aoc_service_dev *dev; s = service_at_index(prvdata, index); if (!s) return NULL; dev = kzalloc(sizeof(struct aoc_service_dev), GFP_KERNEL); if (!dev) return NULL; prvdata->services[index] = dev; name = aoc_service_name(s); if (!name) return NULL; memcpy_fromio(service_name, name, sizeof(service_name)); dev_set_name(&dev->dev, "%s", service_name); dev->dev.parent = parent; dev->dev.bus = &aoc_bus_type; dev->dev.release = aoc_device_release; dev->service_index = index; dev->mbox_index = aoc_service_irq_index(s); dev->service = s; dev->ipc_base = prvdata->ipc_base; dev->dead = false; if (aoc_service_is_queue(s)) dev->wake_capable = true; init_waitqueue_head(&dev->read_queue); init_waitqueue_head(&dev->write_queue); return dev; } static int aoc_iommu_fault_handler(struct iommu_fault *fault, void *token) { struct device *dev = token; dev_err(dev, "aoc iommu fault: fault->type = %u\n", fault->type); dev_err(dev, "fault->event: reason = %u, flags = %#010x, addr = %#010llx\n", fault->event.reason, fault->event.flags, fault->event.addr); dev_err(dev, "fault->prm: flags = %#010x, addr = %#010llx\n", fault->prm.flags, fault->prm.addr); /* Tell the IOMMU driver that the fault is non-fatal. */ return -EAGAIN; } static void aoc_configure_sysmmu_fault_handler(struct aoc_prvdata *p) { struct device *dev = p->dev; int rc = iommu_register_device_fault_handler(dev, aoc_iommu_fault_handler, dev); if (rc) dev_err(dev, "iommu_register_device_fault_handler failed: rc = %d\n", rc); } static void aoc_configure_sysmmu(struct aoc_prvdata *p, const struct firmware *fw) { int rc; size_t i, j, cnt; struct sysmmu_entry *sysmmu; struct iommu_domain *domain = p->domain; struct device *dev = p->dev; u16 sysmmu_offset, sysmmu_size; if (p->sysmmu_configured && p->sysmmu_config_persistent) { dev_info(dev, "SysMMU already configured skipping\n"); return; } aoc_configure_sysmmu_fault_handler(p); sysmmu_offset = _aoc_fw_sysmmu_offset(fw); sysmmu_size = _aoc_fw_sysmmu_size(fw); if (!_aoc_fw_is_valid_sysmmu_size(fw)) { dev_warn(dev, "Invalid sysmmu table (0x%x @ 0x%x)\n", sysmmu_size, sysmmu_offset); return; } cnt = sysmmu_size / sizeof(struct sysmmu_entry); sysmmu = _aoc_fw_sysmmu_entry(fw); p->sysmmu_size = sysmmu_size; p->sysmmu = devm_kzalloc(dev, sysmmu_size, GFP_KERNEL); if (!p->sysmmu) return; memcpy(p->sysmmu, sysmmu, sysmmu_size); for (i = 0; i < cnt; i++) { rc = iommu_map(domain, SYSMMU_VADDR(sysmmu[i].value), SYSMMU_PADDR(sysmmu[i].value), SYSMMU_SIZE(sysmmu[i].value), IOMMU_READ | IOMMU_WRITE); if (rc < 0) { dev_err( dev, "Failed configuring sysmmu: [err=%d] [index:%zu, vaddr: 0x%llx, paddr: 0x%llx, size: 0x%llx]\n", rc, i, SYSMMU_VADDR(sysmmu[i].value), SYSMMU_PADDR(sysmmu[i].value), SYSMMU_SIZE(sysmmu[i].value)); for (j = 0; j < i; j++) { rc = iommu_unmap(domain, SYSMMU_VADDR(sysmmu[j].value), SYSMMU_SIZE(sysmmu[j].value)); if (rc < 0) dev_err(dev, "Failed unmapping sysmmu: %d\n", rc); } return; } } p->sysmmu_configured = true; } static void aoc_clear_sysmmu(struct aoc_prvdata *p) { int rc; struct iommu_domain *domain = p->domain; size_t i, cnt; struct device *dev = p->dev; if (p->sysmmu != NULL) { cnt = p->sysmmu_size / sizeof(struct sysmmu_entry); for (i = 0; i < cnt; i++) { rc = iommu_unmap(domain, SYSMMU_VADDR(p->sysmmu[i].value), SYSMMU_SIZE(p->sysmmu[i].value)); if (rc < 0) dev_err(dev, "Failed umapping sysmmu: %d\n", rc); } } } static void aoc_monitor_online(struct work_struct *work) { struct aoc_prvdata *prvdata = container_of(work, struct aoc_prvdata, monitor_work.work); bool skip_reset = of_property_read_bool(prvdata->dev->of_node, "skip-monitor-online-reset"); if (aoc_state == AOC_STATE_FIRMWARE_LOADED) { dev_err(prvdata->dev, "aoc init no respond, try restart\n"); if (skip_reset) /* TODO: figure out if this still causes APC watchdogs on GS201 */ return; disable_irq_nosync(prvdata->watchdog_irq); strlcpy(prvdata->ap_reset_reason, "Monitor Reset", AP_RESET_REASON_LENGTH); prvdata->ap_triggered_reset = true; schedule_work(&prvdata->watchdog_work); } } static void aoc_did_become_online(struct work_struct *work) { struct aoc_prvdata *prvdata = container_of(work, struct aoc_prvdata, online_work); struct device *dev = prvdata->dev; int i, s, ret; cancel_delayed_work_sync(&prvdata->monitor_work); mutex_lock(&aoc_service_lock); s = aoc_num_services(); request_aoc_on(prvdata, false); pr_notice("firmware version %s did become online with %d services\n", prvdata->firmware_version ? prvdata->firmware_version : "0", aoc_num_services()); if (s > AOC_MAX_ENDPOINTS) { dev_err(dev, "Firmware supports too many (%d) services\n", s); goto err; } if (!service_names_are_valid(prvdata)) { pr_err("invalid service names found. Ignoring\n"); goto err; } for (i = 0; i < s; i++) { if (!validate_service(prvdata, i)) { pr_err("service %d invalid\n", i); goto err; } } prvdata->services = devm_kcalloc(prvdata->dev, s, sizeof(struct aoc_service_dev *), GFP_KERNEL); if (!prvdata->services) { dev_err(prvdata->dev, "failed to allocate service array\n"); goto err; } prvdata->total_services = s; if (prvdata->read_blocked_mask == NULL) { prvdata->read_blocked_mask = devm_kcalloc(prvdata->dev, BITS_TO_LONGS(s), sizeof(unsigned long), GFP_KERNEL); if (!prvdata->read_blocked_mask) goto err; } if (prvdata->write_blocked_mask == NULL) { prvdata->write_blocked_mask = devm_kcalloc(prvdata->dev, BITS_TO_LONGS(s), sizeof(unsigned long), GFP_KERNEL); if (!prvdata->write_blocked_mask) goto err; } for (i = 0; i < s; i++) { if (!create_service_device(prvdata, i)) { dev_err(prvdata->dev, "failed to create service device at index %d\n", i); goto err; } } aoc_state = AOC_STATE_ONLINE; for (i = 0; i < s; i++) { ret = device_register(&prvdata->services[i]->dev); if (ret) dev_err(dev, "failed to register service device %s err=%d\n", dev_name(&prvdata->services[i]->dev), ret); } err: mutex_unlock(&aoc_service_lock); } static bool configure_sensor_regulator(struct aoc_prvdata *prvdata, bool enable) { bool check_enabled; int i; if (enable) { check_enabled = true; for (i = 0; i < prvdata->sensor_power_count; i++) { if (!prvdata->sensor_regulator[i] || regulator_is_enabled(prvdata->sensor_regulator[i])) { continue; } if (regulator_enable(prvdata->sensor_regulator[i])) { pr_warn("encountered error on enabling %s.", prvdata->sensor_power_list[i]); } check_enabled &= regulator_is_enabled(prvdata->sensor_regulator[i]); } } else { check_enabled = false; for (i = prvdata->sensor_power_count - 1; i >= 0; i--) { if (!prvdata->sensor_regulator[i] || !regulator_is_enabled(prvdata->sensor_regulator[i])) { continue; } if (regulator_disable(prvdata->sensor_regulator[i])) { pr_warn("encountered error on disabling %s.", prvdata->sensor_power_list[i]); } check_enabled |= regulator_is_enabled(prvdata->sensor_regulator[i]); } } return (check_enabled == enable); } static bool configure_dmic_regulator(struct aoc_prvdata *prvdata, bool enable) { bool check_enabled; int i; if (enable) { check_enabled = true; for (i = 0; i < prvdata->dmic_power_count; i++) { if (!prvdata->dmic_regulator[i] || regulator_is_enabled(prvdata->dmic_regulator[i])) { continue; } if (regulator_enable(prvdata->dmic_regulator[i])) { pr_warn("encountered error on enabling %s.", prvdata->dmic_power_list[i]); } check_enabled &= regulator_is_enabled(prvdata->dmic_regulator[i]); } } else { check_enabled = false; for (i = prvdata->dmic_power_count - 1; i >= 0; i--) { if (!prvdata->dmic_regulator[i] || !regulator_is_enabled(prvdata->dmic_regulator[i])) { continue; } if (regulator_disable(prvdata->dmic_regulator[i])) { pr_warn(" encountered error on disabling %s.", prvdata->dmic_power_list[i]); } check_enabled |= regulator_is_enabled(prvdata->dmic_regulator[i]); } } return (check_enabled == enable); } static void aoc_parse_dmic_power(struct aoc_prvdata *prvdata, struct device_node *node) { int i; prvdata->dmic_power_count = of_property_count_strings(node, "dmic_power_list"); if (prvdata->dmic_power_count > MAX_DMIC_POWER_NUM) { pr_warn("dmic power count %i is larger than available number.", prvdata->dmic_power_count); prvdata->dmic_power_count = MAX_DMIC_POWER_NUM; } else if (prvdata->dmic_power_count < 0) { pr_err("unsupported dmic power list, err = %i.", prvdata->dmic_power_count); prvdata->dmic_power_count = 0; return; } of_property_read_string_array(node, "dmic_power_list", (const char **)&prvdata->dmic_power_list, prvdata->dmic_power_count); for (i = 0; i < prvdata->dmic_power_count; i++) { prvdata->dmic_regulator[i] = devm_regulator_get_exclusive(prvdata->dev, prvdata->dmic_power_list[i]); if (IS_ERR_OR_NULL(prvdata->dmic_regulator[i])) { prvdata->dmic_regulator[i] = NULL; pr_err("failed to get %s regulator.", prvdata->dmic_power_list[i]); } } } void reset_sensor_power(struct aoc_prvdata *prvdata, bool is_init) { const int max_retry = 5; int count; bool success; if (prvdata->sensor_power_count == 0) { return; } if (!is_init) { count = 0; success = false; while (!success && count < max_retry) { success = configure_sensor_regulator(prvdata, false); count++; } if (!success) { pr_err("failed to disable sensor power after %d retry.", max_retry); } else { pr_info("sensor power is disabled."); } msleep(150); } count = 0; success = false; while (!success && count < max_retry) { success = configure_sensor_regulator(prvdata, true); count++; } if (!success) { pr_err("failed to enable sensor power after %d retry.", max_retry); } else { pr_info("sensor power is enabled."); } } static void aoc_take_offline(struct aoc_prvdata *prvdata) { int rc; /* check if devices/services are ready */ if (aoc_state == AOC_STATE_ONLINE) { pr_notice("taking aoc offline\n"); aoc_state = AOC_STATE_OFFLINE; /* wait until aoc_process or service write/read finish */ while (!!atomic_read(&prvdata->aoc_process_active)) { bus_for_each_dev(&aoc_bus_type, NULL, NULL, aoc_wakeup_queues); } bus_for_each_dev(&aoc_bus_type, NULL, NULL, aoc_remove_device); if (aoc_control) aoc_control->magic = 0; if (prvdata->services) { devm_kfree(prvdata->dev, prvdata->services); prvdata->services = NULL; prvdata->total_services = 0; } /* wakeup AOC before calling GSA */ request_aoc_on(prvdata, true); rc = wait_for_aoc_status(prvdata, true); if (rc) { dev_err(prvdata->dev, "timed out waiting for aoc_ack\n"); if (prvdata->protected_by_gsa) dev_err(prvdata->dev, "skipping GSA commands"); notify_timeout_aoc_status(); return; } } if(prvdata->protected_by_gsa) { /* TODO(b/275463650): GSA_AOC_SHUTDOWN needs to be 4, but the current * header defines as 2. Change this to enum when the header is updated. */ rc = gsa_send_aoc_cmd(prvdata->gsa_dev, 4); /* rc is the new state of AOC unless it's negative, * in which case it's an error code */ if(rc != GSA_AOC_STATE_LOADED) { if(rc >= 0) { dev_err(prvdata->dev, "GSA shutdown command returned unexpected state: %d\n", rc); } else { dev_err(prvdata->dev, "GSA shutdown command returned error: %d\n", rc); } } rc = gsa_unload_aoc_fw_image(prvdata->gsa_dev); if (rc) dev_err(prvdata->dev, "GSA unload firmware failed: %d\n", rc); } } static void aoc_process_services(struct aoc_prvdata *prvdata, int offset) { struct aoc_service_dev *service_dev; aoc_service *service; int services; int i; atomic_inc(&prvdata->aoc_process_active); if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) goto exit; services = aoc_num_services(); for (i = 0; i < services; i++) { service_dev = service_dev_at_index(prvdata, i); if (!service_dev) goto exit; service = service_dev->service; if (service_dev->mbox_index != offset) continue; if (service_dev->handler) { service_dev->handler(service_dev); } else { if (test_bit(i, prvdata->read_blocked_mask) && aoc_service_can_read_message(service, AOC_UP)) wake_up(&service_dev->read_queue); if (test_bit(i, prvdata->write_blocked_mask) && aoc_service_can_write_message(service, AOC_DOWN)) wake_up(&service_dev->write_queue); } } exit: atomic_dec(&prvdata->aoc_process_active); } void notify_timeout_aoc_status(void) { if (aoc_platform_device == NULL) { pr_err("AOC platform device is undefined, can't notify aocd\n"); return; } sysfs_notify(&aoc_platform_device->dev.kobj, NULL, "notify_timeout_aoc_status"); } void aoc_set_map_handler(struct aoc_service_dev *dev, aoc_map_handler handler, void *ctx) { struct device *parent = dev->dev.parent; struct aoc_prvdata *prvdata = dev_get_drvdata(parent); prvdata->map_handler = handler; prvdata->map_handler_ctx = ctx; } EXPORT_SYMBOL_GPL(aoc_set_map_handler); void aoc_remove_map_handler(struct aoc_service_dev *dev) { struct device *parent = dev->dev.parent; struct aoc_prvdata *prvdata = dev_get_drvdata(parent); prvdata->map_handler = NULL; prvdata->map_handler_ctx = NULL; } EXPORT_SYMBOL_GPL(aoc_remove_map_handler); static void aoc_watchdog(struct work_struct *work) { struct aoc_prvdata *prvdata = container_of(work, struct aoc_prvdata, watchdog_work); struct aoc_ramdump_header *ramdump_header = (struct aoc_ramdump_header *)((unsigned long)prvdata->dram_virt + RAMDUMP_HEADER_OFFSET); struct wakeup_source *ws = wakeup_source_register(prvdata->dev, dev_name(prvdata->dev)); unsigned long ramdump_timeout; unsigned long carveout_paddr_from_aoc; unsigned long carveout_vaddr_from_aoc; size_t i; bool skip_carveout_map = of_property_read_bool(prvdata->dev->of_node, "skip-carveout-map"); size_t num_pages; struct page **dram_pages = NULL; void *dram_cached = NULL; int sscd_retries = 20; const int sscd_retry_ms = 1000; int sscd_rc; char crash_info[RAMDUMP_SECTION_CRASH_INFO_SIZE]; int restart_rc; bool ap_reset = false, valid_magic; struct aoc_section_header *crash_info_section; prvdata->total_restarts++; /* Initialize crash_info[0] to identify if it has changed later in the function. */ crash_info[0] = 0; if (prvdata->ap_triggered_reset) { if ((ktime_get_real_ns() - prvdata->last_reset_time_ns) / 1000000 <= prvdata->reset_hysteresis_trigger_ms) { /* If the watchdog was triggered recently, busy wait to * avoid overlapping resets. */ dev_err(prvdata->dev, "Triggered hysteresis for AP reset, waiting %d ms", RESET_WAIT_TIME_MS + prvdata->reset_wait_time_index * RESET_WAIT_TIME_INCREMENT_MS); msleep(RESET_WAIT_TIME_MS + prvdata->reset_wait_time_index * RESET_WAIT_TIME_INCREMENT_MS); if (prvdata->reset_wait_time_index < RESET_WAIT_TIMES_NUM) prvdata->reset_wait_time_index++; } else { prvdata->reset_wait_time_index = 0; } } prvdata->last_reset_time_ns = ktime_get_real_ns(); sscd_info.name = "aoc"; sscd_info.seg_count = 0; dev_err(prvdata->dev, "aoc watchdog triggered, generating coredump\n"); dev_err(prvdata->dev, "holding %s wakelock for 10 sec\n", ws->name); pm_wakeup_ws_event(ws, 10000, true); if (!sscd_pdata.sscd_report) { dev_err(prvdata->dev, "aoc coredump failed: no sscd driver\n"); goto err_coredump; } if (prvdata->ap_triggered_reset) { dev_info(prvdata->dev, "AP triggered reset, reason: [%s]", prvdata->ap_reset_reason); prvdata->ap_triggered_reset = false; ap_reset = true; trigger_aoc_ramdump(prvdata); } ramdump_timeout = jiffies + (5 * HZ); while (time_before(jiffies, ramdump_timeout)) { valid_magic = memcmp(ramdump_header, RAMDUMP_MAGIC, sizeof(RAMDUMP_MAGIC)) == 0; if (ramdump_header->valid == 1 && valid_magic) break; msleep(100); } crash_info_section = &ramdump_header->sections[RAMDUMP_SECTION_CRASH_INFO_INDEX]; if (crash_info_section->type != SECTION_TYPE_CRASH_INFO) crash_info_section = NULL; if (!(ramdump_header->valid == 1) || !valid_magic) { if (!(ramdump_header->valid == 1)) dev_info(prvdata->dev, "aoc coredump timed out, coredump only contains DRAM\n"); if (!valid_magic) dev_info(prvdata->dev, "aoc coredump has invalid magic\n"); if (crash_info_section) { const char *crash_reason = (const char *)ramdump_header + crash_info_section->offset; bool crash_reason_valid = crash_reason < (char *)prvdata->dram_virt + prvdata->dram_size && crash_reason[0] != 0; snprintf(crash_info, sizeof(crash_info), "AoC watchdog : %s (incomplete %u:%u)", crash_reason_valid ? crash_reason : "unknown reason", ramdump_header->breadcrumbs[0], ramdump_header->breadcrumbs[1]); } else { dev_err(prvdata->dev, "could not find crash info section in aoc coredump header"); snprintf(crash_info, sizeof(crash_info), "AoC watchdog : unknown reason (incomplete %u:%u)", ramdump_header->breadcrumbs[0], ramdump_header->breadcrumbs[1]); } } if (ramdump_header->valid == 1 && valid_magic) { if (crash_info_section && crash_info_section->flags & RAMDUMP_FLAG_VALID) { const char *crash_reason = (const char *)ramdump_header + crash_info_section->offset; dev_info(prvdata->dev, "aoc coredump has valid coredump header, crash reason [%s]", crash_reason); strscpy(crash_info, crash_reason, sizeof(crash_info)); } else { dev_info(prvdata->dev, "aoc coredump has valid coredump header, but invalid crash reason"); strscpy(crash_info, "AoC Watchdog : invalid crash info", sizeof(crash_info)); } } if (!skip_carveout_map) { /* In some cases, we don't map AoC carveout as cached due to b/240786634 */ num_pages = DIV_ROUND_UP(prvdata->dram_size, PAGE_SIZE); dram_pages = vmalloc(num_pages * sizeof(*dram_pages)); if (!dram_pages) { dev_err(prvdata->dev, "aoc coredump failed: alloc dram_pages failed\n"); goto err_vmalloc; } for (i = 0; i < num_pages; i++) dram_pages[i] = phys_to_page(prvdata->dram_resource.start + (i * PAGE_SIZE)); dram_cached = vmap(dram_pages, num_pages, VM_MAP, PAGE_KERNEL_RO); if (!dram_cached) { dev_err(prvdata->dev, "aoc coredump failed: vmap dram_pages failed\n"); goto err_vmap; } sscd_info.segs[0].addr = dram_cached; } else { sscd_info.segs[0].addr = prvdata->dram_virt; } if (ap_reset) { /* Prefer the user specified reason */ scnprintf(crash_info, sizeof(crash_info), "AP Reset: %s", prvdata->ap_reset_reason); } if (crash_info[0] == 0) strscpy(crash_info, "AoC Watchdog: empty crash info string", sizeof(crash_info)); dev_info(prvdata->dev, "aoc crash info: [%s]", crash_info); /* TODO(siqilin): Get paddr and vaddr base from firmware instead */ carveout_paddr_from_aoc = 0x98000000; carveout_vaddr_from_aoc = 0x78000000; sscd_info.segs[0].size = prvdata->dram_size; sscd_info.segs[0].paddr = (void *)carveout_paddr_from_aoc; sscd_info.segs[0].vaddr = (void *)carveout_vaddr_from_aoc; sscd_info.seg_count = 1; /* * sscd_report() returns -EAGAIN if there are no readers to consume a * coredump. Retry sscd_report() with a sleep to handle the race condition * where AoC crashes before the userspace daemon starts running. */ for (i = 0; i <= sscd_retries; i++) { sscd_rc = sscd_pdata.sscd_report(&sscd_dev, sscd_info.segs, sscd_info.seg_count, SSCD_FLAGS_ELFARM64HDR, crash_info); if (sscd_rc != -EAGAIN) break; msleep(sscd_retry_ms); } if (sscd_rc == 0) { prvdata->total_coredumps++; dev_info(prvdata->dev, "aoc coredump done\n"); } else { dev_err(prvdata->dev, "aoc coredump failed: sscd_rc = %d\n", sscd_rc); } if (dram_cached) vunmap(dram_cached); err_vmap: if (dram_pages) vfree(dram_pages); err_vmalloc: err_coredump: /* make sure there is no AoC startup work active */ cancel_work_sync(&prvdata->online_work); mutex_lock(&aoc_service_lock); aoc_take_offline(prvdata); restart_rc = aoc_watchdog_restart(prvdata, &aoc_module_params); if (restart_rc == AOC_RESTART_DISABLED_RC) { dev_info(prvdata->dev, "aoc subsystem restart is disabled\n"); } else if (restart_rc) { dev_info(prvdata->dev, "aoc subsystem restart failed: rc = %d\n", restart_rc); } else { dev_info(prvdata->dev, "aoc subsystem restart succeeded\n"); } mutex_unlock(&aoc_service_lock); } void aoc_trigger_watchdog(const char *reason) { struct aoc_prvdata *prvdata; if (!aoc_platform_device) return; prvdata = platform_get_drvdata(aoc_platform_device); if (!prvdata) return; if (work_busy(&prvdata->watchdog_work)) return; reset_store(prvdata->dev, NULL, reason, strlen(reason)); } EXPORT_SYMBOL_GPL(aoc_trigger_watchdog); static int aoc_open(struct inode *inode, struct file *file) { struct aoc_prvdata *prvdata = container_of(inode->i_cdev, struct aoc_prvdata, cdev); file->private_data = prvdata; return 0; } static long aoc_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct aoc_prvdata *prvdata = file->private_data; long ret = -EINVAL; switch (cmd) { case AOC_IOCTL_ION_FD_TO_HANDLE: { ret = aoc_unlocked_ioctl_handle_ion_fd(cmd, arg); if (ret == -EINVAL) { pr_err("invalid argument\n"); } } break; case AOC_IOCTL_DISABLE_MM: { u32 disable_mm; BUILD_BUG_ON(sizeof(disable_mm) != _IOC_SIZE(AOC_IOCTL_DISABLE_MM)); if (copy_from_user(&disable_mm, (u32 *)arg, _IOC_SIZE(cmd))) break; prvdata->disable_monitor_mode = disable_mm; if (prvdata->disable_monitor_mode != 0) pr_info("AoC Monitor Mode disabled\n"); ret = 0; } break; case AOC_IOCTL_DISABLE_AP_RESETS: { u32 disable_ap_resets; BUILD_BUG_ON(sizeof(disable_ap_resets) != _IOC_SIZE(AOC_IOCTL_DISABLE_AP_RESETS)); if (copy_from_user(&disable_ap_resets, (u32 *)arg, _IOC_SIZE(cmd))) break; prvdata->no_ap_resets = disable_ap_resets; if (prvdata->no_ap_resets != 0) pr_info("AoC AP side resets disabled\n"); ret = 0; } break; case AOC_IOCTL_FORCE_VNOM: { u32 force_vnom; BUILD_BUG_ON(sizeof(force_vnom) != _IOC_SIZE(AOC_IOCTL_FORCE_VNOM)); if (copy_from_user(&force_vnom, (u32 *)arg, _IOC_SIZE(cmd))) break; prvdata->force_voltage_nominal = force_vnom; if (prvdata->force_voltage_nominal != 0) pr_info("AoC Force Nominal Voltage enabled\n"); ret = 0; } break; case AOC_IOCTL_ENABLE_UART_TX: { u32 enable_uart; BUILD_BUG_ON(sizeof(enable_uart) != _IOC_SIZE(AOC_IOCTL_ENABLE_UART_TX)); if (copy_from_user(&enable_uart, (u32 *)arg, _IOC_SIZE(cmd))) break; prvdata->enable_uart_tx = enable_uart; if (prvdata->enable_uart_tx != 0) pr_info("AoC UART Logging Enabled\n"); ret = 0; } break; case AOC_IOCTL_FORCE_SPEAKER_ULTRASONIC: { u32 force_sprk_ultrasonic; BUILD_BUG_ON(sizeof(force_sprk_ultrasonic) != _IOC_SIZE(AOC_IOCTL_FORCE_SPEAKER_ULTRASONIC)); if (copy_from_user(&force_sprk_ultrasonic, (u32 *)arg, _IOC_SIZE(cmd))) break; prvdata->force_speaker_ultrasonic = force_sprk_ultrasonic; if (prvdata->force_speaker_ultrasonic != 0) pr_info("AoC Forcefully enabling Speaker Ultrasonic pipeline\n"); ret = 0; } break; case AOC_IS_ONLINE: { int online = (aoc_state == AOC_STATE_ONLINE); if (!copy_to_user((int *)arg, &online, _IOC_SIZE(cmd))) ret = 0; } break; default: /* ioctl(2) The specified request does not apply to the kind of object * that the file descriptor fd references */ pr_err("Received IOCTL with invalid ID (%d) returning ENOTTY", cmd); ret = -ENOTTY; break; } return ret; } static int aoc_release(struct inode *inode, struct file *file) { return 0; } static const struct file_operations aoc_fops = { .open = aoc_open, .release = aoc_release, .unlocked_ioctl = aoc_unlocked_ioctl, .owner = THIS_MODULE, }; static char *aoc_devnode(struct device *dev, umode_t *mode) { if (!mode || !dev) return NULL; if (MAJOR(dev->devt) == aoc_major) *mode = 0666; return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); } static int init_chardev(struct aoc_prvdata *prvdata) { int rc; cdev_init(&prvdata->cdev, &aoc_fops); prvdata->cdev.owner = THIS_MODULE; rc = alloc_chrdev_region(&prvdata->aoc_devt, 0, AOC_MAX_MINOR, AOC_CHARDEV_NAME); if (rc != 0) { pr_err("Failed to alloc chrdev region\n"); goto err; } rc = cdev_add(&prvdata->cdev, prvdata->aoc_devt, AOC_MAX_MINOR); if (rc) { pr_err("Failed to register chrdev\n"); goto err_cdev_add; } aoc_major = MAJOR(prvdata->aoc_devt); prvdata->_class = class_create(THIS_MODULE, AOC_CHARDEV_NAME); if (!prvdata->_class) { pr_err("failed to create aoc_class\n"); rc = -ENXIO; goto err_class_create; } prvdata->_class->devnode = aoc_devnode; prvdata->_device = device_create(prvdata->_class, NULL, MKDEV(aoc_major, 0), NULL, AOC_CHARDEV_NAME); if (!prvdata->_device) { pr_err("failed to create aoc_device\n"); rc = -ENXIO; goto err_device_create; } return rc; err_device_create: class_destroy(prvdata->_class); err_class_create: cdev_del(&prvdata->cdev); err_cdev_add: unregister_chrdev_region(prvdata->aoc_devt, 1); err: return rc; } static void deinit_chardev(struct aoc_prvdata *prvdata) { if (!prvdata) return; device_destroy(prvdata->_class, prvdata->aoc_devt); class_destroy(prvdata->_class); cdev_del(&prvdata->cdev); unregister_chrdev_region(prvdata->aoc_devt, AOC_MAX_MINOR); } static void aoc_cleanup_resources(struct platform_device *pdev) { struct aoc_prvdata *prvdata = platform_get_drvdata(pdev); pr_notice("cleaning up resources\n"); if (prvdata) { aoc_take_offline(prvdata); free_mailbox_channels(prvdata); if (prvdata->domain) { aoc_clear_sysmmu(prvdata); prvdata->domain = NULL; } } } static void release_gsa_device(void *prv) { struct device *gsa_device = (struct device *)prv; put_device(gsa_device); } static int find_gsa_device(struct aoc_prvdata *prvdata) { struct device_node *np; struct platform_device *gsa_pdev; np = of_parse_phandle(prvdata->dev->of_node, "gsa-device", 0); if (!np) { dev_err(prvdata->dev, "gsa-device phandle not found in AOC device tree node\n"); return -ENODEV; } gsa_pdev = of_find_device_by_node(np); of_node_put(np); if (!gsa_pdev) { dev_err(prvdata->dev, "gsa-device phandle doesn't refer to a device\n"); return -ENODEV; } prvdata->gsa_dev = &gsa_pdev->dev; return devm_add_action_or_reset(prvdata->dev, release_gsa_device, &gsa_pdev->dev); } static int aoc_core_suspend(struct device *dev) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); size_t total_services = aoc_num_services(); int i = 0; atomic_inc(&prvdata->aoc_process_active); if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) goto exit; for (i = 0; i < total_services; i++) { struct aoc_service_dev *s = service_dev_at_index(prvdata, i); if (s && s->wake_capable) s->suspend_rx_count = aoc_service_slots_available_to_read(s->service, AOC_UP); } exit: atomic_dec(&prvdata->aoc_process_active); return 0; } static int aoc_core_resume(struct device *dev) { struct aoc_prvdata *prvdata = dev_get_drvdata(dev); size_t total_services = aoc_num_services(); int i = 0; atomic_inc(&prvdata->aoc_process_active); if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) goto exit; for (i = 0; i < total_services; i++) { struct aoc_service_dev *s = service_dev_at_index(prvdata, i); if (s && s->wake_capable) { size_t available = aoc_service_slots_available_to_read(s->service, AOC_UP); if (available != s->suspend_rx_count) dev_notice(dev, "Service \"%s\" has %zu messages to read on wake\n", dev_name(&s->dev), available); } } exit: atomic_dec(&prvdata->aoc_process_active); return 0; } static int platform_probe_parse_dt(struct device *dev, struct device_node *aoc_node) { struct aoc_prvdata *prvdata = platform_get_drvdata(aoc_platform_device); prvdata->aoc_pcu_base = dt_property(aoc_node, "pcu-base"); if (prvdata->aoc_pcu_base == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property pcu-base"); return -EINVAL; } prvdata->aoc_gpio_base = dt_property(aoc_node, "gpio-base"); if (prvdata->aoc_gpio_base == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property gpio-base"); return -EINVAL; } prvdata->aoc_pcu_db_set_offset = dt_property(aoc_node, "pcu-db-set-offset"); if (prvdata->aoc_pcu_db_set_offset == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property pcu-db-set-offset"); return -EINVAL; } prvdata->aoc_pcu_db_clr_offset = dt_property(aoc_node, "pcu-db-clr-offset"); if (prvdata->aoc_pcu_db_clr_offset == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property pcu-db-clr-offset"); return -EINVAL; } prvdata->aoc_cp_aperture_start_offset = dt_property(aoc_node, "cp-aperture-start-offset"); if (prvdata->aoc_cp_aperture_start_offset == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property cp-aperture-start-offset"); return -EINVAL; } prvdata->aoc_cp_aperture_end_offset = dt_property(aoc_node, "cp-aperture-end-offset"); if (prvdata->aoc_cp_aperture_end_offset == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property cp-aperture-end-offset"); return -EINVAL; } prvdata->aoc_clock_divider = dt_property(aoc_node, "clock-divider"); if (prvdata->aoc_clock_divider == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property clock-divider"); return -EINVAL; } prvdata->aoc_mbox_channels = dt_property(aoc_node, "mbox-channels"); if (prvdata->aoc_mbox_channels == DT_PROPERTY_NOT_FOUND) { dev_err(dev, "AOC DT missing property mbox-channels"); return -EINVAL; } prvdata->sysmmu_config_persistent = of_property_read_bool(aoc_node, "sysmmu-config-persistent"); return 0; } static int aoc_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct aoc_prvdata *prvdata = NULL; struct device_node *aoc_node, *mem_node, *sysmmu_node; struct resource *rsrc; int ret; int rc; int i; if (aoc_platform_device != NULL) { dev_err(dev, "already matched the AoC to another platform device"); rc = -EEXIST; goto err_platform_not_null; } aoc_platform_device = pdev; aoc_node = dev->of_node; mem_node = of_parse_phandle(aoc_node, "memory-region", 0); prvdata = devm_kzalloc(dev, sizeof(*prvdata), GFP_KERNEL); if (!prvdata) { rc = -ENOMEM; goto err_failed_prvdata_alloc; } platform_set_drvdata(pdev, prvdata); if (platform_probe_parse_dt(dev, aoc_node) < 0) { rc = -EINVAL; goto err_invalid_dt; } prvdata->mbox_channels = devm_kzalloc(dev, sizeof(struct mbox_slot) * prvdata->aoc_mbox_channels, GFP_KERNEL); if (!prvdata->mbox_channels) { rc = -ENOMEM; goto err_failed_prvdata_alloc; } prvdata->dev = dev; prvdata->disable_monitor_mode = 0; prvdata->enable_uart_tx = 0; prvdata->force_voltage_nominal = 0; prvdata->no_ap_resets = 0; prvdata->reset_hysteresis_trigger_ms = 10000; prvdata->last_reset_time_ns = ktime_get_real_ns(); prvdata->reset_wait_time_index = 0; rc = find_gsa_device(prvdata); if (rc) { dev_err(dev, "Failed to initialize gsa device: %d\n", rc); rc = -EINVAL; goto err_failed_prvdata_alloc; } ret = init_chardev(prvdata); if (ret) { dev_err(dev, "Failed to initialize chardev: %d\n", ret); rc = -ENOMEM; goto err_chardev; } if (!mem_node) { dev_err(dev, "failed to find reserve-memory in the device tree\n"); rc = -EINVAL; goto err_memnode; } aoc_sram_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "blk_aoc"); ret = of_address_to_resource(mem_node, 0, &prvdata->dram_resource); of_node_put(mem_node); if (!aoc_sram_resource || ret != 0) { dev_err(dev, "failed to get memory resources for device sram %pR dram %pR\n", aoc_sram_resource, &prvdata->dram_resource); rc = -ENOMEM; goto err_mem_resources; } for (i = 0; i < prvdata->aoc_mbox_channels; i++) { prvdata->mbox_channels[i].client.dev = dev; prvdata->mbox_channels[i].client.tx_block = false; prvdata->mbox_channels[i].client.tx_tout = 100; /* 100ms timeout for tx */ prvdata->mbox_channels[i].client.knows_txdone = false; prvdata->mbox_channels[i].client.rx_callback = aoc_mbox_rx_callback; prvdata->mbox_channels[i].client.tx_done = aoc_mbox_tx_done; prvdata->mbox_channels[i].client.tx_prepare = aoc_mbox_tx_prepare; prvdata->mbox_channels[i].prvdata = prvdata; prvdata->mbox_channels[i].index = i; } strscpy(prvdata->firmware_name, default_firmware, sizeof(prvdata->firmware_name)); rc = allocate_mailbox_channels(prvdata); if (rc) { dev_err(dev, "failed to allocate mailbox channels %d\n", rc); goto err_mem_resources; } init_waitqueue_head(&prvdata->aoc_reset_wait_queue); INIT_WORK(&prvdata->watchdog_work, aoc_watchdog); ret = configure_watchdog_interrupt(pdev, prvdata); if (ret < 0) goto err_watchdog_irq; sysmmu_node = of_parse_phandle(aoc_node, "iommus", 0); if (!sysmmu_node) { dev_err(dev, "failed to find sysmmu device tree node\n"); rc = -ENODEV; goto err_watchdog_sysmmu_irq; } ret = configure_sysmmu_interrupts(dev, sysmmu_node, prvdata); if (ret < 0) goto err_watchdog_sysmmu_irq; of_node_put(sysmmu_node); pr_notice("found aoc with interrupt:%d sram:%pR dram:%pR\n", aoc_irq, aoc_sram_resource, &prvdata->dram_resource); aoc_sram_virt_mapping = devm_ioremap_resource(dev, aoc_sram_resource); prvdata->dram_size = resource_size(&prvdata->dram_resource); if (!devm_request_mem_region(dev, prvdata->dram_resource.start, prvdata->dram_size, dev_name(dev))) { dev_err(dev, "Failed to claim dram resource %pR\n", &prvdata->dram_resource); rc = -EIO; goto err_sram_dram_map; } aoc_dram_virt_mapping = devm_ioremap_wc(dev, prvdata->dram_resource.start, prvdata->dram_size); /* Change to devm_platform_ioremap_resource_byname when available */ rsrc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aoc_req"); if (rsrc) { prvdata->aoc_req_virt = devm_ioremap_resource(dev, rsrc); prvdata->aoc_req_size = resource_size(rsrc); if (IS_ERR(prvdata->aoc_req_virt)) { dev_err(dev, "failed to map aoc_req region at %pR\n", rsrc); prvdata->aoc_req_virt = NULL; prvdata->aoc_req_size = 0; } else { dev_dbg(dev, "found aoc_req at %pR\n", rsrc); } } prvdata->sram_virt = aoc_sram_virt_mapping; prvdata->sram_size = resource_size(aoc_sram_resource); prvdata->dram_virt = aoc_dram_virt_mapping; if (IS_ERR(aoc_sram_virt_mapping) || IS_ERR(aoc_dram_virt_mapping)) { rc = -ENOMEM; goto err_sram_dram_map; } prvdata->aoc_s2mpu_virt = devm_platform_ioremap_resource_byname(pdev, "aoc_s2mpu"); if (IS_ERR(prvdata->aoc_s2mpu_virt)) { dev_err(dev, "failed to map aoc_s2mpu: rc = %ld\n", PTR_ERR(prvdata->aoc_s2mpu_virt)); rc = PTR_ERR(prvdata->aoc_s2mpu_virt); goto err_s2mpu_map; } prvdata->aoc_s2mpu_saved_value = ioread32(prvdata->aoc_s2mpu_virt + AOC_S2MPU_CTRL0); pm_runtime_set_active(dev); /* Leave AoC in suspended state. Otherwise, AoC SysMMU is set to active which results in the * SysMMU driver trying to access SysMMU SFRs during device suspend/resume operations. The * latter is problematic if AoC is in monitor mode and BLK_AOC is off. */ pm_runtime_set_suspended(dev); prvdata->domain = iommu_get_domain_for_dev(dev); if (!prvdata->domain) { pr_err("failed to find iommu domain\n"); rc = -EIO; goto err_find_iommu; } aoc_configure_ssmt(pdev); if (!aoc_create_dma_buf_heaps(prvdata)) { pr_err("Unable to create dma_buf heaps\n"); aoc_cleanup_resources(pdev); return -ENOMEM; } prvdata->sensor_power_count = of_property_count_strings(aoc_node, "sensor_power_list"); if (prvdata->sensor_power_count > MAX_SENSOR_POWER_NUM) { pr_warn("sensor power count %i is larger than available number.", prvdata->sensor_power_count); prvdata->sensor_power_count = MAX_SENSOR_POWER_NUM; } else if (prvdata->sensor_power_count < 0) { pr_err("unsupported sensor power list, err = %i.", prvdata->sensor_power_count); prvdata->sensor_power_count = 0; } ret = of_property_read_string_array(aoc_node, "sensor_power_list", (const char**)&prvdata->sensor_power_list, prvdata->sensor_power_count); for (i = 0; i < prvdata->sensor_power_count; i++) { prvdata->sensor_regulator[i] = devm_regulator_get_exclusive(dev, prvdata->sensor_power_list[i]); if (IS_ERR_OR_NULL(prvdata->sensor_regulator[i])) { prvdata->sensor_regulator[i] = NULL; pr_err("failed to get %s regulator.", prvdata->sensor_power_list[i]); } } reset_sensor_power(prvdata, true); aoc_parse_dmic_power(prvdata, aoc_node); configure_dmic_regulator(prvdata, true); /* Default to 6MB if we are not loading the firmware (i.e. trace32) */ aoc_control = aoc_dram_translate(prvdata, 6 * SZ_1M); INIT_WORK(&prvdata->online_work, aoc_did_become_online); INIT_DELAYED_WORK(&prvdata->monitor_work, aoc_monitor_online); aoc_configure_hardware(prvdata); rc = platform_specific_probe(pdev, prvdata); if (aoc_autoload_firmware) { ret = start_firmware_load(dev); if (ret != 0) pr_err("failed to start firmware download: %d\n", ret); } ret = sysfs_create_groups(&dev->kobj, aoc_groups); pr_debug("platform_probe matched\n"); return 0; /* err_acmp_reset: */ err_find_iommu: err_s2mpu_map: err_sram_dram_map: err_watchdog_sysmmu_irq: err_watchdog_irq: err_mem_resources: aoc_cleanup_resources(pdev); err_memnode: deinit_chardev(prvdata); err_chardev: err_failed_prvdata_alloc: err_invalid_dt: aoc_platform_device = NULL; err_platform_not_null: return rc; } static int aoc_platform_remove(struct platform_device *pdev) { struct aoc_prvdata *prvdata; int i; pr_debug("platform_remove\n"); prvdata = platform_get_drvdata(pdev); acpm_ipc_release_channel(pdev->dev.of_node, prvdata->acpm_async_id); for (i = 0; i < prvdata->sensor_power_count; i++) { if (prvdata->sensor_regulator[i]) { regulator_put(prvdata->sensor_regulator[i]); } } sysfs_remove_groups(&pdev->dev.kobj, aoc_groups); aoc_cleanup_resources(pdev); deinit_chardev(prvdata); platform_set_drvdata(pdev, NULL); aoc_platform_device = NULL; return 0; } static void sscd_release(struct device *dev) { } static void aoc_platform_shutdown(struct platform_device *pdev) { struct aoc_prvdata *prvdata = platform_get_drvdata(pdev); configure_crash_interrupts(prvdata, false); aoc_take_offline(prvdata); } /* Module methods */ static int __init aoc_init(void) { pr_debug("system driver init\n"); if (bus_register(&aoc_bus_type) != 0) { pr_err("failed to register AoC bus\n"); goto err_aoc_bus; } if (platform_driver_register(&aoc_driver) != 0) { pr_err("failed to register platform driver\n"); goto err_aoc_driver; } if (platform_device_register(&sscd_dev) != 0) { pr_err("failed to register AoC coredump device\n"); goto err_aoc_coredump; } return 0; err_aoc_coredump: platform_driver_unregister(&aoc_driver); err_aoc_driver: bus_unregister(&aoc_bus_type); err_aoc_bus: return -ENODEV; } static void __exit aoc_exit(void) { pr_debug("system driver exit\n"); platform_driver_unregister(&aoc_driver); bus_unregister(&aoc_bus_type); } module_init(aoc_init); module_exit(aoc_exit); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS(DMA_BUF);