| // 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 <linux/atomic.h> |
| #include <linux/cdev.h> |
| #include <linux/delay.h> |
| #include <linux/dma-map-ops.h> |
| #include <linux/firmware.h> |
| #include <linux/fs.h> |
| #include <linux/glob.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/iommu.h> |
| #include <linux/jiffies.h> |
| #include <linux/kernel.h> |
| #include <linux/list.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_data/sscoredump.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/timer.h> |
| #include <linux/uaccess.h> |
| #include <linux/uio.h> |
| #include <linux/wait.h> |
| #include <linux/workqueue.h> |
| #include <linux/mutex.h> |
| #include <soc/google/acpm_ipc_ctrl.h> |
| #include <soc/google/debug-snapshot.h> |
| #include <soc/google/exynos-cpupm.h> |
| #include <soc/google/exynos-pmu-if.h> |
| |
| #include <linux/gsa/gsa_aoc.h> |
| |
| #if IS_ENABLED(CONFIG_EXYNOS_ITMON) |
| #include <soc/google/exynos-itmon.h> |
| #endif |
| |
| #include "ion_physical_heap.h" |
| |
| #include "aoc_firmware.h" |
| #include "aoc_ipc_core.h" |
| #include "aoc_ramdump_regions.h" |
| |
| /* TODO: Remove internal calls, or promote to "public" */ |
| #include "aoc_ipc_core_internal.h" |
| |
| /* This should not be required, as we expect only one of the two to be defined */ |
| #if IS_ENABLED(CONFIG_SOC_GS201) |
| #undef CONFIG_SOC_GS101 |
| #endif |
| |
| #if IS_ENABLED(CONFIG_SOC_GS201) && IS_ENABLED(CONFIG_SOC_GS101) |
| #error "GS201 and GS101 are mutually exclusive" |
| #endif |
| |
| #define MAX_FIRMWARE_LENGTH 128 |
| #define AP_RESET_REASON_LENGTH 32 |
| #define AOC_S2MPU_CTRL0 0x0 |
| |
| #define AOC_MAX_MINOR (1U) |
| #define AOC_MBOX_CHANNELS 16 |
| |
| #define AOC_FWDATA_ENTRIES 10 |
| #define AOC_FWDATA_BOARDID_DFL 0x20202 |
| #define AOC_FWDATA_BOARDREV_DFL 0x10000 |
| |
| #define SENSOR_DIRECT_HEAP_SIZE SZ_4M |
| #define PLAYBACK_HEAP_SIZE SZ_16K |
| #define CAPTURE_HEAP_SIZE SZ_64K |
| |
| #define MAX_RESET_REASON_STRING_LEN 128UL |
| |
| #define MAX_SENSOR_POWER_NUM 5 |
| |
| #if IS_ENABLED(CONFIG_SOC_GS201) |
| #define AOC_PCU_BASE AOC_PCU_BASE_PRO |
| #define AOC_GPIO_BASE AOC_GPIO_BASE_PRO |
| #define AOC_CP_APERTURE_START_OFFSET 0x7FDF80 |
| #define AOC_CP_APERTURE_END_OFFSET 0x7FFFFF |
| #define AOC_CLOCK_DIVIDER 1 |
| #elif IS_ENABLED(CONFIG_SOC_GS101) |
| #define AOC_PCU_BASE AOC_PCU_BASE_WC |
| #define AOC_GPIO_BASE AOC_GPIO_BASE_WC |
| #define AOC_CP_APERTURE_START_OFFSET 0x5FDF80 |
| #define AOC_CP_APERTURE_END_OFFSET 0x5FFFFF |
| #define GPIO_INTERRUPT 93 |
| #define AOC_CLOCK_DIVIDER 6 |
| #endif |
| |
| #define MAX_SENSOR_POWER_NUM 5 |
| |
| static DEFINE_MUTEX(aoc_service_lock); |
| |
| enum AOC_FW_STATE { |
| AOC_STATE_OFFLINE = 0, |
| AOC_STATE_FIRMWARE_LOADED, |
| AOC_STATE_STARTING, |
| AOC_STATE_ONLINE |
| }; |
| static enum AOC_FW_STATE aoc_state; |
| |
| static struct platform_device *aoc_platform_device; |
| |
| |
| struct mbox_slot { |
| struct mbox_client client; |
| struct mbox_chan *channel; |
| void *prvdata; |
| int index; |
| }; |
| |
| struct aoc_prvdata { |
| struct mbox_slot mbox_channels[AOC_MBOX_CHANNELS]; |
| struct aoc_service_dev **services; |
| |
| struct work_struct online_work; |
| struct resource dram_resource; |
| aoc_map_handler map_handler; |
| void *map_handler_ctx; |
| |
| struct delayed_work monitor_work; |
| atomic_t aoc_process_active; |
| |
| struct device *dev; |
| struct iommu_domain *domain; |
| void *ipc_base; |
| |
| void *sram_virt; |
| void *dram_virt; |
| void *aoc_req_virt; |
| void *aoc_s2mpu_virt; |
| size_t sram_size; |
| size_t dram_size; |
| size_t aoc_req_size; |
| u32 aoc_s2mpu_saved_value; |
| |
| struct dma_heap *sensor_heap; |
| struct dma_heap *audio_playback_heap; |
| struct dma_heap *audio_capture_heap; |
| phys_addr_t sensor_heap_base; |
| phys_addr_t audio_playback_heap_base; |
| phys_addr_t audio_capture_heap_base; |
| |
| int watchdog_irq; |
| struct work_struct watchdog_work; |
| bool aoc_reset_done; |
| bool ap_triggered_reset; |
| char ap_reset_reason[AP_RESET_REASON_LENGTH]; |
| wait_queue_head_t aoc_reset_wait_queue; |
| unsigned int acpm_async_id; |
| int total_services; |
| |
| char firmware_name[MAX_FIRMWARE_LENGTH]; |
| char *firmware_version; |
| |
| struct cdev cdev; |
| dev_t aoc_devt; |
| struct class *_class; |
| struct device *_device; |
| |
| u32 disable_monitor_mode; |
| u32 enable_uart_tx; |
| u32 force_voltage_nominal; |
| u32 no_ap_resets; |
| u32 force_speaker_ultrasonic; |
| |
| u32 total_coredumps; |
| u32 total_restarts; |
| unsigned int sysmmu_nonsecure_irq; |
| unsigned int sysmmu_secure_irq; |
| |
| #if IS_ENABLED(CONFIG_EXYNOS_ITMON) |
| struct notifier_block itmon_nb; |
| #endif |
| struct device *gsa_dev; |
| |
| int sensor_power_count; |
| const char *sensor_power_list[MAX_SENSOR_POWER_NUM]; |
| struct regulator *sensor_regulator[MAX_SENSOR_POWER_NUM]; |
| }; |
| |
| /* TODO: Reduce the global variables (move into a driver structure) */ |
| /* Resources found from the device tree */ |
| static struct resource *aoc_sram_resource; |
| |
| struct sscd_info { |
| char *name; |
| struct sscd_segment segs[256]; |
| u16 seg_count; |
| }; |
| |
| static void trigger_aoc_ramdump(struct aoc_prvdata *prvdata); |
| 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; |
| |
| static struct aoc_control_block *aoc_control; |
| |
| static int aoc_major; |
| |
| static const char *default_firmware = "aoc.bin"; |
| static bool aoc_autoload_firmware; |
| module_param(aoc_autoload_firmware, bool, 0644); |
| MODULE_PARM_DESC(aoc_autoload_firmware, "Automatically load firmware if true"); |
| |
| 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 int aoc_bus_remove(struct device *dev); |
| |
| 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 unsigned long read_blocked_mask; |
| static unsigned long write_blocked_mask; |
| |
| static bool aoc_fpga_reset(struct aoc_prvdata *prvdata); |
| static bool write_reset_trampoline(u32 addr); |
| static bool aoc_a32_reset(void); |
| static int aoc_watchdog_restart(struct aoc_prvdata *prvdata); |
| static void acpm_aoc_reset_callback(unsigned int *cmd, unsigned int size); |
| |
| static int start_firmware_load(struct device *dev); |
| static void aoc_take_offline(struct aoc_prvdata *prvdata); |
| static void signal_aoc(struct mbox_chan *channel); |
| static void reset_sensor_power(struct aoc_prvdata *prvdata, bool is_init); |
| |
| static void aoc_process_services(struct aoc_prvdata *prvdata, int offset); |
| |
| static irqreturn_t watchdog_int_handler(int irq, void *dev); |
| static void aoc_watchdog(struct work_struct *work); |
| |
| #if IS_ENABLED(CONFIG_EXYNOS_ITMON) |
| static int aoc_itmon_notifier(struct notifier_block *nb, unsigned long action, |
| void *nb_data) |
| { |
| struct aoc_prvdata *prvdata; |
| struct itmon_notifier *itmon_info = nb_data; |
| |
| prvdata = container_of(nb, struct aoc_prvdata, itmon_nb); |
| if (itmon_info->port && (strncmp(itmon_info->port, "AOC", sizeof("AOC") - 1) == 0)) |
| return NOTIFY_STOP; |
| |
| if (itmon_info->target_addr == 0) { |
| dev_err(prvdata->dev, |
| "Possible repro of b/174577569, please upload a bugreport and /data/vendor/ssrdump to that bug\n"); |
| return NOTIFY_STOP; |
| } |
| |
| if ((itmon_info->target_addr >= aoc_sram_resource->start + AOC_CP_APERTURE_START_OFFSET) && |
| (itmon_info->target_addr <= aoc_sram_resource->start + AOC_CP_APERTURE_END_OFFSET)) { |
| dev_err(prvdata->dev, |
| "Valid memory access triggered ITMON error. Please file a bug with bugreport and contents of /data/vendor/ssrdump\n"); |
| return NOTIFY_STOP; |
| } |
| |
| return NOTIFY_OK; |
| } |
| #endif |
| |
| static inline 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; |
| } |
| |
| static 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); |
| } |
| |
| static inline 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; |
| } |
| |
| static inline bool aoc_fw_ready(void) |
| { |
| return aoc_control != NULL && aoc_control->magic == AOC_MAGIC; |
| } |
| |
| static inline int aoc_num_services(void) |
| { |
| return aoc_fw_ready() ? le32_to_cpu(aoc_control->services) : 0; |
| } |
| |
| static inline aoc_service *service_at_index(struct aoc_prvdata *prvdata, |
| unsigned index) |
| { |
| if (!aoc_fw_ready() || index > aoc_num_services()) |
| return NULL; |
| |
| return (((uint8_t *)prvdata->ipc_base) + aoc_control->services_offset + |
| (le32_to_cpu(aoc_control->service_size) * index)); |
| } |
| |
| static inline struct aoc_service_dev *service_dev_at_index(struct aoc_prvdata *prvdata, unsigned index) |
| { |
| if (!aoc_fw_ready() || index > aoc_num_services() || aoc_state != AOC_STATE_ONLINE) |
| return NULL; |
| |
| return prvdata->services[index]; |
| } |
| |
| static bool validate_service(struct aoc_prvdata *prv, int i) |
| { |
| struct aoc_ipc_service_header *hdr = service_at_index(prv, i); |
| struct device *dev = prv->dev; |
| |
| if (!aoc_is_valid_dram_address(prv, hdr)) { |
| dev_err(dev, "service %d is not in DRAM region\n", i); |
| return false; |
| } |
| |
| if (hdr->regions[0].slots == 0 && hdr->regions[1].slots == 0) { |
| dev_err(dev, "service %d is not readable or writable\n", i); |
| |
| return false; |
| } |
| |
| if (aoc_service_is_ring(hdr) && |
| (hdr->regions[0].slots > 1 || hdr->regions[1].slots > 1)) { |
| dev_err(dev, "service %d has invalid ring slot configuration\n", |
| i); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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 < ARRAY_SIZE(prv->mbox_channels); i++) { |
| slot = &prv->mbox_channels[i]; |
| slot->channel = mbox_request_channel(&slot->client, i); |
| if (IS_ERR(slot->channel)) { |
| dev_err(dev, "failed to find mailbox interface %d : %ld\n", i, |
| PTR_ERR(slot->channel)); |
| slot->channel = NULL; |
| rc = -EIO; |
| 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 < ARRAY_SIZE(prv->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) |
| { |
| } |
| |
| static void aoc_req_assert(struct aoc_prvdata *p, bool assert) |
| { |
| iowrite32(!!assert, p->aoc_req_virt); |
| } |
| |
| static int aoc_req_wait(struct aoc_prvdata *p, bool assert) |
| { |
| unsigned long aoc_req_timeout; |
| |
| aoc_req_timeout = jiffies + (2 * HZ); |
| while (time_before(jiffies, aoc_req_timeout)) { |
| if (!!readl(p->aoc_req_virt + 0x40) == !!assert) |
| return 0; |
| msleep(100); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| extern int gs_chipid_get_ap_hw_tune_array(const u8 **array); |
| |
| #if IS_ENABLED(CONFIG_SOC_GS101) |
| static bool aoc_sram_was_repaired(struct aoc_prvdata *prvdata) |
| { |
| const u8 *array; |
| struct device *dev = prvdata->dev; |
| int ret; |
| |
| ret = gs_chipid_get_ap_hw_tune_array(&array); |
| |
| if (ret == -EPROBE_DEFER) { |
| dev_err(dev, "Unable to determine SRAM repair state. Leaving monitor mode disabled\n"); |
| return false; |
| } |
| |
| if (ret != 32) { |
| dev_err(dev, "Unexpected hw_tune_array size. Leaving monitor mode disabled\n"); |
| return false; |
| } |
| |
| /* Bit 65 says that AoC SRAM was repaired */ |
| return ((array[8] & 0x2) != 0); |
| } |
| #else |
| static inline bool aoc_sram_was_repaired(struct aoc_prvdata *prvdata) { return false; } |
| #endif |
| |
| struct aoc_fw_data { |
| u32 key; |
| u32 value; |
| }; |
| |
| static u32 dt_property(struct device_node *node, const char *key) |
| { |
| u32 ret; |
| |
| if (of_property_read_u32(node, key, &ret)) |
| return 0xffffffff; |
| |
| return ret; |
| } |
| |
| 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) |
| { |
| 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; |
| 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; |
| |
| 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 } |
| }; |
| const char *version; |
| u32 fw_data_entries = ARRAY_SIZE(fw_data); |
| u32 ipc_offset, bootloader_offset; |
| |
| aoc_board_config_parse(prvdata->dev->of_node, &board_id, &board_rev); |
| |
| if (!fw) { |
| dev_err(dev, "failed to load firmware image\n"); |
| return; |
| } |
| |
| 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; |
| } |
| |
| aoc_req_assert(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); |
| bootloader_offset = _aoc_fw_bootloader_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); |
| |
| dev_info(dev, "Loading %s aoc image\n", fw_signed ? "signed" : "unsigned"); |
| |
| aoc_control = aoc_dram_translate(prvdata, ipc_offset); |
| |
| aoc_fpga_reset(prvdata); |
| |
| _aoc_fw_commit(fw, aoc_dram_virt_mapping + AOC_BINARY_DRAM_OFFSET); |
| |
| if (fw_signed) { |
| int rc = aoc_fw_authenticate(prvdata, fw); |
| if (rc) { |
| dev_err(dev, "GSA: FW authentication failed: %d\n", rc); |
| goto free_fw; |
| } |
| } else { |
| write_reset_trampoline(AOC_BINARY_LOAD_ADDRESS + bootloader_offset); |
| } |
| |
| aoc_pass_fw_information(aoc_dram_translate(prvdata, ipc_offset), |
| fw_data, ARRAY_SIZE(fw_data)); |
| |
| aoc_state = AOC_STATE_FIRMWARE_LOADED; |
| |
| dev_info(dev, "disabling SICD 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 (fw_signed) { |
| 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_a32_reset(); |
| } |
| |
| enable_irq(prvdata->watchdog_irq); |
| |
| /* 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 SICD\n"); |
| enable_power_mode(0, POWERMODE_TYPE_SYSTEM); |
| |
| free_fw: |
| 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):%llx\n", audio_heap_base); |
| |
| return audio_heap_base; |
| } |
| EXPORT_SYMBOL_GPL(aoc_get_heap_base_phys_addr); |
| |
| bool aoc_service_flush_read_data(struct aoc_service_dev *dev) |
| { |
| const struct device *parent; |
| struct aoc_prvdata *prvdata; |
| aoc_service *service; |
| size_t slots; |
| |
| if (!dev) |
| return false; |
| |
| parent = dev->dev.parent; |
| prvdata = dev_get_drvdata(parent); |
| |
| service = service_at_index(prvdata, dev->service_index); |
| |
| slots = aoc_service_slots_available_to_read(service, AOC_UP); |
| if (slots == 0) |
| return false; |
| |
| aoc_service_advance_read_index(service, AOC_UP, slots); |
| return true; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_flush_read_data); |
| |
| ssize_t aoc_service_read(struct aoc_service_dev *dev, uint8_t *buffer, |
| size_t count, bool block) |
| { |
| const struct device *parent; |
| struct aoc_prvdata *prvdata; |
| aoc_service *service; |
| |
| size_t msg_size; |
| int service_number; |
| int ret = 0; |
| |
| if (!dev || !buffer || !count) |
| return -EINVAL; |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (aoc_state != AOC_STATE_ONLINE) |
| return -EBUSY; |
| |
| parent = dev->dev.parent; |
| prvdata = dev_get_drvdata(parent); |
| |
| service_number = dev->service_index; |
| service = service_at_index(prvdata, dev->service_index); |
| |
| BUG_ON(!aoc_is_valid_dram_address(prvdata, service)); |
| |
| if (aoc_service_message_slots(service, AOC_UP) == 0) |
| return -EBADF; |
| |
| if (!aoc_service_can_read_message(service, AOC_UP)) { |
| if (!block) |
| return -EAGAIN; |
| |
| set_bit(service_number, &read_blocked_mask); |
| ret = wait_event_interruptible(dev->read_queue, |
| aoc_state != AOC_STATE_ONLINE || dev->dead || |
| aoc_service_can_read_message(service, AOC_UP)); |
| clear_bit(service_number, &read_blocked_mask); |
| } |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (aoc_state != AOC_STATE_ONLINE) |
| return -ENODEV; |
| |
| /* |
| * The wait can fail if the AoC goes offline in the middle of a |
| * blocking read, so check again after the wait |
| */ |
| if (ret != 0) |
| return -EAGAIN; |
| |
| if (!aoc_service_is_ring(service) && |
| count < aoc_service_current_message_size(service, prvdata->ipc_base, |
| AOC_UP)) |
| return -EFBIG; |
| |
| msg_size = count; |
| aoc_service_read_message(service, prvdata->ipc_base, AOC_UP, buffer, |
| &msg_size); |
| |
| return msg_size; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_read); |
| |
| ssize_t aoc_service_read_timeout(struct aoc_service_dev *dev, uint8_t *buffer, |
| size_t count, long timeout) |
| { |
| struct aoc_prvdata *prvdata; |
| aoc_service *service; |
| |
| size_t msg_size; |
| int service_number; |
| long ret = 1; |
| |
| if (!dev || !buffer || !count) |
| return -EINVAL; |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (!aoc_platform_device) |
| return -ENODEV; |
| |
| prvdata = dev_get_drvdata(dev->dev.parent); |
| if (!prvdata) |
| return -ENODEV; |
| |
| atomic_inc(&prvdata->aoc_process_active); |
| if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) { |
| ret = -EBUSY; |
| goto err; |
| } |
| |
| service_number = dev->service_index; |
| service = service_at_index(prvdata, dev->service_index); |
| |
| if (!aoc_is_valid_dram_address(prvdata, service)) { |
| WARN_ONCE(1, "aoc service %d has invalid DRAM region", service_number); |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| if (aoc_service_message_slots(service, AOC_UP) == 0) { |
| ret = -EBADF; |
| goto err; |
| } |
| |
| if (!aoc_service_can_read_message(service, AOC_UP)) { |
| set_bit(service_number, &read_blocked_mask); |
| ret = wait_event_interruptible_timeout( |
| dev->read_queue, |
| aoc_state != AOC_STATE_ONLINE || dev->dead || |
| aoc_service_can_read_message(service, AOC_UP), |
| timeout); |
| clear_bit(service_number, &read_blocked_mask); |
| } |
| |
| if (dev->dead || (aoc_state != AOC_STATE_ONLINE)) { |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| if (ret < 0) |
| goto err; |
| |
| /* AoC timed out */ |
| if (ret == 0) { |
| ret = -ETIMEDOUT; |
| goto err; |
| } |
| |
| if (!aoc_service_is_ring(service) && |
| count < aoc_service_current_message_size(service, prvdata->ipc_base, |
| AOC_UP)) { |
| ret = -EFBIG; |
| goto err; |
| } |
| |
| msg_size = count; |
| aoc_service_read_message(service, prvdata->ipc_base, AOC_UP, buffer, |
| &msg_size); |
| |
| err: |
| atomic_dec(&prvdata->aoc_process_active); |
| |
| if (ret < 0) |
| return ret; |
| |
| return msg_size; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_read_timeout); |
| |
| ssize_t aoc_service_write(struct aoc_service_dev *dev, const uint8_t *buffer, |
| size_t count, bool block) |
| { |
| const struct device *parent; |
| struct aoc_prvdata *prvdata; |
| |
| aoc_service *service; |
| int service_number; |
| int interrupt = dev->mbox_index; |
| int ret = 0; |
| |
| if (!dev || !buffer || !count) |
| return -EINVAL; |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (aoc_state != AOC_STATE_ONLINE) |
| return -ENODEV; |
| |
| if (interrupt >= AOC_MBOX_CHANNELS) |
| return -EINVAL; |
| |
| BUG_ON(!dev->dev.parent); |
| |
| parent = dev->dev.parent; |
| prvdata = dev_get_drvdata(parent); |
| |
| service_number = dev->service_index; |
| service = service_at_index(prvdata, service_number); |
| |
| BUG_ON(!aoc_is_valid_dram_address(prvdata, service)); |
| |
| if (aoc_service_message_slots(service, AOC_DOWN) == 0) |
| return -EBADF; |
| |
| if (count > aoc_service_message_size(service, AOC_DOWN)) |
| return -EFBIG; |
| |
| if (!aoc_service_can_write_message(service, AOC_DOWN)) { |
| if (!block) |
| return -EAGAIN; |
| |
| set_bit(service_number, &write_blocked_mask); |
| ret = wait_event_interruptible(dev->write_queue, |
| aoc_state != AOC_STATE_ONLINE || dev->dead || |
| aoc_service_can_write_message(service, AOC_DOWN)); |
| clear_bit(service_number, &write_blocked_mask); |
| } |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (aoc_state != AOC_STATE_ONLINE) |
| return -ENODEV; |
| |
| /* |
| * The wait can fail if the AoC goes offline in the middle of a |
| * blocking write, so check again after the wait |
| */ |
| if (ret != 0) |
| return -EAGAIN; |
| |
| ret = aoc_service_write_message(service, prvdata->ipc_base, AOC_DOWN, |
| buffer, count); |
| |
| if (!aoc_service_is_ring(service) || aoc_ring_is_push(service)) |
| signal_aoc(prvdata->mbox_channels[interrupt].channel); |
| |
| return count; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_write); |
| |
| ssize_t aoc_service_write_timeout(struct aoc_service_dev *dev, const uint8_t *buffer, |
| size_t count, long timeout) |
| { |
| struct aoc_prvdata *prvdata; |
| |
| aoc_service *service; |
| int service_number; |
| int interrupt = dev->mbox_index; |
| long ret = 1; |
| |
| if (!dev || !buffer || !count) |
| return -EINVAL; |
| |
| if (dev->dead) |
| return -ENODEV; |
| |
| if (!aoc_platform_device) |
| return -ENODEV; |
| |
| prvdata = dev_get_drvdata(dev->dev.parent); |
| if (!prvdata) |
| return -ENODEV; |
| |
| atomic_inc(&prvdata->aoc_process_active); |
| if (aoc_state != AOC_STATE_ONLINE || work_busy(&prvdata->watchdog_work)) { |
| ret = -EBUSY; |
| goto err; |
| } |
| |
| service_number = dev->service_index; |
| service = service_at_index(prvdata, service_number); |
| |
| if (!aoc_is_valid_dram_address(prvdata, service)) { |
| WARN_ONCE(1, "aoc service %d has invalid DRAM region", service_number); |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| if (aoc_service_message_slots(service, AOC_DOWN) == 0) { |
| ret = -EBADF; |
| goto err; |
| } |
| |
| if (count > aoc_service_message_size(service, AOC_DOWN)) { |
| ret = -EFBIG; |
| goto err; |
| } |
| |
| if (!aoc_service_can_write_message(service, AOC_DOWN)) { |
| set_bit(service_number, &write_blocked_mask); |
| ret = wait_event_interruptible_timeout( |
| dev->write_queue, |
| aoc_state != AOC_STATE_ONLINE || dev->dead || |
| aoc_service_can_write_message(service, AOC_DOWN), |
| timeout); |
| clear_bit(service_number, &write_blocked_mask); |
| } |
| |
| if (dev->dead || (aoc_state != AOC_STATE_ONLINE)) { |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| if (ret < 0) |
| goto err; |
| |
| if (ret == 0) { |
| ret = -ETIMEDOUT; |
| goto err; |
| } |
| |
| ret = aoc_service_write_message(service, prvdata->ipc_base, AOC_DOWN, |
| buffer, count); |
| |
| if (!aoc_service_is_ring(service) || aoc_ring_is_push(service)) |
| signal_aoc(prvdata->mbox_channels[interrupt].channel); |
| |
| err: |
| atomic_dec(&prvdata->aoc_process_active); |
| |
| if (ret < 0) |
| return ret; |
| |
| return count; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_write_timeout); |
| |
| int aoc_service_can_read(struct aoc_service_dev *dev) |
| { |
| const struct device *parent; |
| struct aoc_prvdata *prvdata; |
| aoc_service *service; |
| |
| parent = dev->dev.parent; |
| prvdata = dev_get_drvdata(parent); |
| service = service_at_index(prvdata, dev->service_index); |
| |
| if (aoc_service_message_slots(service, AOC_UP) == 0) |
| return 0; |
| |
| return aoc_service_can_read_message(service, AOC_UP); |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_can_read); |
| |
| int aoc_service_can_write(struct aoc_service_dev *dev) |
| { |
| const struct device *parent; |
| struct aoc_prvdata *prvdata; |
| aoc_service *service; |
| |
| parent = dev->dev.parent; |
| prvdata = dev_get_drvdata(parent); |
| service = service_at_index(prvdata, dev->service_index); |
| |
| if (aoc_service_message_slots(service, AOC_DOWN) == 0) |
| return 0; |
| |
| return aoc_service_can_write_message(service, AOC_DOWN); |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_can_write); |
| |
| void aoc_service_set_read_blocked(struct aoc_service_dev *dev) |
| { |
| int service_number; |
| |
| service_number = dev->service_index; |
| set_bit(service_number, &read_blocked_mask); |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_set_read_blocked); |
| |
| void aoc_service_set_write_blocked(struct aoc_service_dev *dev) |
| { |
| int service_number; |
| |
| service_number = dev->service_index; |
| set_bit(service_number, &write_blocked_mask); |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_set_write_blocked); |
| |
| wait_queue_head_t *aoc_service_get_read_queue(struct aoc_service_dev *dev) |
| { |
| return &dev->read_queue; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_get_read_queue); |
| |
| wait_queue_head_t *aoc_service_get_write_queue(struct aoc_service_dev *dev) |
| { |
| return &dev->write_queue; |
| } |
| EXPORT_SYMBOL_GPL(aoc_service_get_write_queue); |
| |
| static bool write_reset_trampoline(u32 addr) |
| { |
| u32 *reset; |
| u32 instructions[] = { |
| /* <start>: */ |
| /* 0: */ 0xe59f004c, /* ldr r0, [pc, #76] ; 54 <.PCU_SLC_MIF_REQ_ADDR> */ |
| /* 4: */ 0xe59f104c, /* ldr r1, [pc, #76] ; 58 <.PCU_SLC_MIF_REQ_VALUE> */ |
| /* 8: */ 0xe5801000, /* str r1, [r0] */ |
| /* c: */ 0xe59f0048, /* ldr r0, [pc, #72] ; 5c <.PCU_SLC_MIF_ACK_ADDR> */ |
| /* 10: */ 0xe59f104c, /* ldr r1, [pc, #76] ; 64 <.PCU_SLC_MIF_ACK_VALUE> */ |
| /* 14: */ 0xe59f2044, /* ldr r2, [pc, #68] ; 60 <.PCU_SLC_MIF_ACK_MASK> */ |
| |
| /* <mif_ack_loop>: */ |
| /* 18: */ 0xe5903000, /* ldr r3, [r0] */ |
| /* 1c: */ 0xe0033002, /* and r3, r3, r2 */ |
| /* 20: */ 0xe1530001, /* cmp r3, r1 */ |
| /* 24: */ 0x1afffffb, /* bne 18 <mif_ack_loop> */ |
| |
| /* 28: */ 0xe59f0038, /* ldr r0, [pc, #56] ; 68 <.PCU_BLK_PWR_REQ_ADDR> */ |
| /* 2c: */ 0xe59f1038, /* ldr r1, [pc, #56] ; 6c <.PCU_BLK_PWR_REQ_VALUE> */ |
| /* 30: */ 0xe5801000, /* str r1, [r0] */ |
| /* 34: */ 0xe59f0034, /* ldr r0, [pc, #52] ; 70 <.PCU_BLK_PWR_ACK_ADDR> */ |
| /* 38: */ 0xe59f1038, /* ldr r1, [pc, #56] ; 78 <.PCU_BLK_PWR_ACK_VALUE> */ |
| /* 3c: */ 0xe59f2030, /* ldr r2, [pc, #48] ; 74 <.PCU_BLK_PWR_ACK_MASK> */ |
| |
| /* <blk_aoc_on_loop>: */ |
| /* 40: */ 0xe5903000, /* ldr r3, [r0] */ |
| /* 44: */ 0xe0033002, /* and r3, r3, r2 */ |
| /* 48: */ 0xe1530001, /* cmp r3, r1 */ |
| /* 4c: */ 0x1afffffb, /* bne 40 <blk_aoc_on_loop> */ |
| /* 50: */ 0xe59ff024, /* ldr pc, [pc, #36] ; 7c <.BOOTLOADER_START_ADDR> */ |
| |
| |
| #if IS_ENABLED(CONFIG_SOC_GS201) |
| /* .PCU_SLC_MIF_REQ_ADDR: */ 0xA08000, |
| /* .PCU_SLC_MIF_REQ_VALUE: */ 0x000003, /* Set ACTIVE_REQUEST = 1, MIS_SLCn = 1 to request MIF access */ |
| /* .PCU_SLC_MIF_ACK_ADDR: */ 0xA08004, |
| /* .PCU_SLC_MIF_ACK_MASK: */ 0x000002, /* MIF_ACK field is bit 1 */ |
| /* .PCU_SLC_MIF_ACK_VALUE: */ 0x000002, /* MIF_ACK = ACK, 0x1 (<< 1) */ |
| |
| /* .PCU_BLK_PWR_REQ_ADDR: */ 0xA0103C, |
| /* .PCU_BLK_PWR_REQ_VALUE: */ 0x000001, /* POWER_REQUEST = On, 0x1 (<< 0) */ |
| /* .PCU_BLK_PWR_ACK_ADDR: */ 0xA0103C, |
| /* .PCU_BLK_PWR_ACK_MASK: */ 0x00000C, /* POWER_MODE field is bits 3:2 */ |
| /* .PCU_BLK_PWR_ACK_VALUE: */ 0x000004, /* POWER_MODE = On, 0x1 (<< 2) */ |
| #elif IS_ENABLED(CONFIG_SOC_GS101) |
| /* .PCU_SLC_MIF_REQ_ADDR: */ 0xB0819C, |
| /* .PCU_SLC_MIF_REQ_VALUE: */ 0x000003, /* Set ACTIVE_REQUEST = 1, MIS_SLCn = 1 to request MIF access */ |
| /* .PCU_SLC_MIF_ACK_ADDR: */ 0xB0819C, |
| /* .PCU_SLC_MIF_ACK_MASK: */ 0x000002, /* MIF_ACK field is bit 1 */ |
| /* .PCU_SLC_MIF_ACK_VALUE: */ 0x000002, /* MIF_ACK = ACK, 0x1 (<< 1) */ |
| |
| /* .PCU_BLK_PWR_REQ_ADDR: */ 0xB02004, |
| /* .PCU_BLK_PWR_REQ_VALUE: */ 0x000004, /* BLK_AOC = Initiate Wakeup Sequence, 0x1 (<< 2) */ |
| /* .PCU_BLK_PWR_ACK_ADDR: */ 0xB02000, |
| /* .PCU_BLK_PWR_ACK_MASK: */ 0x000004, /* BLK_AOC field is bit 2 */ |
| /* .PCU_BLK_PWR_ACK_VALUE: */ 0x000004, /* BLK_AOC = Active, 0x1 (<< 2) */ |
| #else |
| #error "Unsupported silicon" |
| #endif |
| /* .BOOTLOADER_START_ADDR: */ addr, |
| }; |
| |
| pr_notice("writing reset trampoline to addr %#x\n", addr); |
| |
| reset = aoc_sram_translate(0); |
| if (!reset) |
| return false; |
| |
| memcpy_toio(reset, instructions, sizeof(instructions)); |
| |
| return true; |
| } |
| |
| static bool aoc_fpga_reset(struct aoc_prvdata *prvdata) |
| { |
| #ifdef AOC_JUNO |
| u32 *reset = aoc_sram_translate(0x1000000); |
| |
| if (!reset) |
| return false; |
| |
| aoc_take_offline(prvdata); |
| |
| /* Assert and deassert reset */ |
| iowrite32(0, reset); |
| iowrite32(1, reset); |
| #endif |
| |
| return true; |
| } |
| |
| static bool aoc_a32_reset(void) |
| { |
| u32 pcu_value; |
| void __iomem *pcu = aoc_sram_translate(AOC_PCU_BASE); |
| |
| if (!pcu) |
| return false; |
| |
| pcu_value = ioread32(pcu); |
| |
| pcu_value |= 1; |
| iowrite32(pcu_value, pcu); |
| |
| return true; |
| } |
| |
| __attribute__((unused)) |
| static int aoc_watchdog_restart(struct aoc_prvdata *prvdata) |
| { |
| /* 4100 * 0.244 us * 100 = 100 ms */ |
| const int aoc_watchdog_value_ssr = 4100 * 100; |
| const int aoc_reset_timeout_ms = 1000; |
| const int aoc_reset_tries = 3; |
| const u32 aoc_watchdog_control_ssr = 0x3F; |
| const unsigned int custom_in_offset = 0x3AC4; |
| const unsigned int custom_out_offset = 0x3AC0; |
| int rc; |
| void __iomem *pcu; |
| unsigned int custom_in; |
| unsigned int custom_out; |
| int ret; |
| bool aoc_reset_successful; |
| int i; |
| |
| pcu = aoc_sram_translate(AOC_PCU_BASE); |
| if (!pcu) |
| return -ENODEV; |
| |
| dev_info(prvdata->dev, "asserting aoc_req\n"); |
| aoc_req_assert(prvdata, true); |
| rc = aoc_req_wait(prvdata, true); |
| if (rc) { |
| dev_err(prvdata->dev, "timed out waiting for aoc_ack\n"); |
| return rc; |
| } |
| |
| aoc_reset_successful = false; |
| disable_irq_nosync(prvdata->sysmmu_nonsecure_irq); |
| disable_irq_nosync(prvdata->sysmmu_secure_irq); |
| for (i = 0; i < aoc_reset_tries; i++) { |
| dev_info(prvdata->dev, "resetting aoc\n"); |
| writel(AOC_PCU_WATCHDOG_KEY_UNLOCK, pcu + AOC_PCU_WATCHDOG_KEY_OFFSET); |
| if ((readl(pcu + AOC_PCU_WATCHDOG_CONTROL_OFFSET) & |
| AOC_PCU_WATCHDOG_CONTROL_KEY_ENABLED_MASK) == 0) { |
| dev_err(prvdata->dev, "unlock aoc watchdog failed\n"); |
| } |
| writel(aoc_watchdog_value_ssr, pcu + AOC_PCU_WATCHDOG_VALUE_OFFSET); |
| writel(aoc_watchdog_control_ssr, pcu + AOC_PCU_WATCHDOG_CONTROL_OFFSET); |
| |
| dev_info(prvdata->dev, "waiting for aoc reset to finish\n"); |
| if (wait_event_timeout(prvdata->aoc_reset_wait_queue, prvdata->aoc_reset_done, |
| aoc_reset_timeout_ms) == 0) { |
| ret = exynos_pmu_read(custom_out_offset, &custom_out); |
| dev_err(prvdata->dev, |
| "AoC reset timeout custom_out=%d, ret=%d\n", custom_out, ret); |
| ret = exynos_pmu_read(custom_in_offset, &custom_in); |
| dev_err(prvdata->dev, |
| "AoC reset timeout custom_in=%d, ret=%d\n", custom_in, ret); |
| dev_err(prvdata->dev, "PCU_WATCHDOG_CONTROL = 0x%x\n", |
| readl(pcu + AOC_PCU_WATCHDOG_CONTROL_OFFSET)); |
| dev_err(prvdata->dev, "PCU_WATCHDOG_VALUE = 0x%x\n", |
| readl(pcu + AOC_PCU_WATCHDOG_VALUE_OFFSET)); |
| } else { |
| aoc_reset_successful = true; |
| break; |
| } |
| } |
| enable_irq(prvdata->sysmmu_nonsecure_irq); |
| enable_irq(prvdata->sysmmu_secure_irq); |
| if (!aoc_reset_successful) { |
| /* Trigger acpm ramdump since we timed out the aoc reset request */ |
| dbg_snapshot_emergency_reboot("AoC Restart timed out"); |
| return -ETIMEDOUT; |
| } |
| reset_sensor_power(prvdata, false); |
| dev_info(prvdata->dev, "aoc reset finished\n"); |
| prvdata->aoc_reset_done = false; |
| |
| /* |
| * AOC_TZPC has been restored by ACPM, so we can access AOC_S2MPU. |
| * Restore AOC_S2MPU. |
| */ |
| writel(prvdata->aoc_s2mpu_saved_value, prvdata->aoc_s2mpu_virt + AOC_S2MPU_CTRL0); |
| |
| /* Restore SysMMU settings by briefly setting AoC to runtime active. Since SysMMU is a |
| * supplier to AoC, it will be set to runtime active as a side effect. */ |
| rc = pm_runtime_set_active(prvdata->dev); |
| if (rc < 0) { |
| dev_err(prvdata->dev, "sysmmu restore failed: pm_runtime_resume rc = %d\n", rc); |
| return rc; |
| } |
| rc = pm_runtime_set_suspended(prvdata->dev); |
| if (rc < 0) { |
| dev_err(prvdata->dev, "sysmmu restore failed: pm_runtime_suspend rc = %d\n", rc); |
| return rc; |
| } |
| |
| rc = start_firmware_load(prvdata->dev); |
| if (rc) { |
| dev_err(prvdata->dev, "load aoc firmware failed: rc = %d\n", rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| static void acpm_aoc_reset_callback(unsigned int *cmd, unsigned int size) |
| { |
| struct aoc_prvdata *prvdata; |
| |
| if (!aoc_platform_device) |
| return; |
| |
| prvdata = platform_get_drvdata(aoc_platform_device); |
| prvdata->aoc_reset_done = true; |
| wake_up(&prvdata->aoc_reset_wait_queue); |
| } |
| |
| 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) |
| { |
| return (sys_tick - clock_offset()) / 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; |
| |
| 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); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static DEVICE_ATTR_RO(services); |
| |
| static 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]; |
| size_t reason_str_len = min(MAX_RESET_REASON_STRING_LEN, count); |
| |
| 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, reason_str_len); |
| reason_str[reason_str_len] = '\0'; |
| 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 { |
| disable_irq_nosync(prvdata->watchdog_irq); |
| 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 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, |
| 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 int aoc_bus_remove(struct device *dev) |
| { |
| struct aoc_service_dev *aoc_dev = AOC_DEVICE(dev); |
| struct aoc_driver *drv = AOC_DRIVER(dev->driver); |
| int ret = -EINVAL; |
| |
| pr_notice("bus remove %s\n", dev_name(dev)); |
| |
| if (drv->remove) |
| ret = drv->remove(aoc_dev); |
| |
| return ret; |
| } |
| |
| 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 void aoc_clear_gpio_interrupt(void) |
| { |
| #if defined(GPIO_INTERRUPT) && !defined(AOC_JUNO) |
| int reg = GPIO_INTERRUPT, val; |
| u32 *gpio_register = |
| aoc_sram_translate(AOC_GPIO_BASE + ((reg / 32) * 12)); |
| |
| val = ioread32(gpio_register); |
| val &= ~(1 << (reg % 32)); |
| iowrite32(val, gpio_register); |
| #endif |
| } |
| |
| static void aoc_configure_interrupt(void) |
| { |
| aoc_clear_gpio_interrupt(); |
| } |
| |
| static int aoc_remove_device(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); |
| |
| 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); |
| 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 void trigger_aoc_ramdump(struct aoc_prvdata *prvdata) |
| { |
| struct mbox_chan *channel = prvdata->mbox_channels[15].channel; |
| static const uint32_t command[] = { 0, 0, 0, 0, 0x0deada0c, 0, 0, 0 }; |
| |
| dev_notice(prvdata->dev, "Attempting to force AoC coredump\n"); |
| |
| mbox_send_message(channel, (void *)&command); |
| } |
| |
| static void signal_aoc(struct mbox_chan *channel) |
| { |
| #ifdef AOC_JUNO |
| (void)channel; |
| |
| u32 mask = (1 << AOC_DOWNCALL_DOORBELL); |
| |
| /* The signal is called as directly after writing a message to shared |
| * memory, so make sure all pending writes are flushed before actually |
| * sending the signal |
| */ |
| wmb(); |
| iowrite32(mask, |
| aoc_sram_translate(AOC_PCU_BASE + AOC_PCU_DB_SET_OFFSET)); |
| #else |
| mbox_send_message(channel, NULL); |
| #endif |
| } |
| |
| 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; |
| } |
| |
| #define SSMT_BYPASS_VALUE 0x80000000U |
| #define SSMT_NS_READ_PID(n) (0x4000 + 4 * (n)) |
| #define SSMT_NS_WRITE_PID(n) (0x4200 + 4 * (n)) |
| |
| #if IS_ENABLED(CONFIG_SOC_GS101) |
| static void aoc_configure_ssmt(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| int stream_id; |
| |
| void __iomem *ssmt_base = devm_platform_ioremap_resource_byname(pdev, "ssmt_aoc"); |
| |
| if (IS_ERR(ssmt_base)) { |
| dev_err(dev, "ssmt_aoc base address failure: %ld\n", PTR_ERR(ssmt_base)); |
| return; |
| } |
| |
| /* Configure registers NS_READ_PID_<n>, NS_WRITE_PID_<n> for each stream id */ |
| for (stream_id = 0; stream_id <= 32; stream_id++) { |
| /* Skip over stream id 31 */ |
| if (stream_id == 31) |
| continue; |
| writel_relaxed(SSMT_BYPASS_VALUE, ssmt_base + SSMT_NS_READ_PID(stream_id)); |
| writel_relaxed(SSMT_BYPASS_VALUE, ssmt_base + SSMT_NS_WRITE_PID(stream_id)); |
| } |
| |
| devm_iounmap(dev, ssmt_base); |
| } |
| #else |
| static inline void aoc_configure_ssmt( struct platform_device *pdev |
| __attribute__((unused))) { } |
| #endif |
| |
| static void aoc_configure_sysmmu(struct aoc_prvdata *p) |
| { |
| #ifndef AOC_JUNO |
| struct iommu_domain *domain = p->domain; |
| struct device *dev = p->dev; |
| int rc; |
| |
| 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); |
| |
| /* Map in the AoC carveout */ |
| if (iommu_map(domain, 0x98000000, p->dram_resource.start, p->dram_size, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping carveout failed\n"); |
| |
| #if IS_ENABLED(CONFIG_SOC_GS201) |
| /* Use a 1MB mapping instead of individual mailboxes for now */ |
| /* TODO: Turn the mailbox address ranges into dtb entries */ |
| if (iommu_map(domain, 0x9E000000, 0x18200000, SZ_2M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping mailboxes failed\n"); |
| |
| /* Map in GSA mailbox */ |
| if (iommu_map(domain, 0x9E200000, 0x17C00000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping gsa mailbox failed\n"); |
| |
| /* Map in modem registers */ |
| if (iommu_map(domain, 0x9E300000, 0x40000000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping modem failed\n"); |
| |
| /* Map in BLK_TPU */ |
| /* if (iommu_map(domain, 0x9E600000, 0x1CE00000, SZ_2M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping mailboxes failed\n"); */ |
| |
| /* Map in the xhci_dma carveout */ |
| if (iommu_map(domain, 0x9B000000, 0x97000000, SZ_4M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping xhci_dma carveout failed\n"); |
| |
| /* Map in USB for low power audio */ |
| if (iommu_map(domain, 0x9E500000, 0x11200000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping usb failed\n"); |
| #else |
| /* Map in the xhci_dma carveout */ |
| if (iommu_map(domain, 0x9B000000, 0x97000000, SZ_4M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping xhci_dma carveout failed\n"); |
| |
| /* Use a 1MB mapping instead of individual mailboxes for now */ |
| /* TODO: Turn the mailbox address ranges into dtb entries */ |
| if (iommu_map(domain, 0x9E000000, 0x17600000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping mailboxes failed\n"); |
| |
| /* Map in GSA mailbox */ |
| if (iommu_map(domain, 0x9E100000, 0x17C00000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping gsa mailbox failed\n"); |
| |
| /* Map in USB for low power audio */ |
| if (iommu_map(domain, 0x9E200000, 0x11100000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping usb failed\n"); |
| |
| /* Map in modem registers */ |
| if (iommu_map(domain, 0x9E300000, 0x40000000, SZ_1M, |
| IOMMU_READ | IOMMU_WRITE)) |
| dev_err(dev, "mapping modem failed\n"); |
| #endif |
| #endif |
| } |
| |
| static void aoc_clear_sysmmu(struct aoc_prvdata *p) |
| { |
| #ifndef AOC_JUNO |
| struct iommu_domain *domain = p->domain; |
| |
| /* Memory carveout */ |
| iommu_unmap(domain, 0x98000000, p->dram_size); |
| iommu_unmap(domain, 0x9B000000, SZ_4M); |
| |
| /* Device registers */ |
| iommu_unmap(domain, 0x9E000000, SZ_1M); |
| iommu_unmap(domain, 0x9E100000, SZ_1M); |
| iommu_unmap(domain, 0x9E200000, SZ_1M); |
| iommu_unmap(domain, 0x9E300000, SZ_1M); |
| #endif |
| } |
| |
| static void aoc_monitor_online(struct work_struct *work) |
| { |
| struct aoc_prvdata *prvdata = |
| container_of(work, struct aoc_prvdata, monitor_work.work); |
| int restart_rc; |
| |
| |
| mutex_lock(&aoc_service_lock); |
| if (aoc_state == AOC_STATE_FIRMWARE_LOADED) { |
| dev_err(prvdata->dev, "aoc init no respond, try restart\n"); |
| |
| aoc_take_offline(prvdata); |
| restart_rc = aoc_watchdog_restart(prvdata); |
| if (restart_rc) |
| dev_info(prvdata->dev, |
| "aoc restart failed: rc = %d\n", restart_rc); |
| else |
| dev_info(prvdata->dev, |
| "aoc restart succeeded\n"); |
| } |
| mutex_unlock(&aoc_service_lock); |
| } |
| |
| 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; |
| |
| cancel_delayed_work_sync(&prvdata->monitor_work); |
| |
| mutex_lock(&aoc_service_lock); |
| |
| s = aoc_num_services(); |
| |
| aoc_req_assert(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; |
| |
| |
| for (i = 0; i < s; i++) { |
| create_service_device(prvdata, i); |
| } |
| |
| aoc_state = AOC_STATE_ONLINE; |
| |
| for (i = 0; i < s; i++) |
| device_register(&prvdata->services[i]->dev); |
| |
| 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 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_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 */ |
| aoc_req_assert(prvdata, true); |
| rc = aoc_req_wait(prvdata, true); |
| if (rc) |
| dev_err(prvdata->dev, "timed out waiting for aoc_ack\n"); |
| } |
| |
| /* TODO: GSA_AOC_SHUTDOWN needs to be 4, but the current header defines |
| * as 2. Change this when the header is updated |
| */ |
| gsa_send_aoc_cmd(prvdata->gsa_dev, 4); |
| 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, &read_blocked_mask) && |
| aoc_service_can_read_message(service, AOC_UP)) |
| wake_up(&service_dev->read_queue); |
| |
| if (test_bit(i, &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 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_pheap_alloc_cb(struct samsung_dma_buffer *buffer, void *ctx) |
| { |
| struct device *dev = ctx; |
| struct aoc_prvdata *prvdata = dev_get_drvdata(dev); |
| struct sg_table *sg = &buffer->sg_table; |
| phys_addr_t phys; |
| size_t size; |
| |
| if (sg->nents != 1) { |
| dev_warn(dev, "Unable to map sg_table with %d ents\n", |
| sg->nents); |
| return; |
| } |
| |
| phys = sg_phys(&sg->sgl[0]); |
| phys = aoc_dram_translate_to_aoc(prvdata, phys); |
| size = sg->sgl[0].length; |
| |
| mutex_lock(&aoc_service_lock); |
| if (prvdata->map_handler) { |
| prvdata->map_handler((u64)buffer->priv, phys, size, true, |
| prvdata->map_handler_ctx); |
| } |
| mutex_unlock(&aoc_service_lock); |
| } |
| |
| static void aoc_pheap_free_cb(struct samsung_dma_buffer *buffer, void *ctx) |
| { |
| struct device *dev = ctx; |
| struct aoc_prvdata *prvdata = dev_get_drvdata(dev); |
| struct sg_table *sg = &buffer->sg_table; |
| phys_addr_t phys; |
| size_t size; |
| |
| if (sg->nents != 1) { |
| dev_warn(dev, "Unable to map sg_table with %d ents\n", |
| sg->nents); |
| return; |
| } |
| |
| phys = sg_phys(&sg->sgl[0]); |
| phys = aoc_dram_translate_to_aoc(prvdata, phys); |
| size = sg->sgl[0].length; |
| |
| mutex_lock(&aoc_service_lock); |
| if (prvdata->map_handler) { |
| prvdata->map_handler((u64)buffer->priv, phys, size, false, |
| prvdata->map_handler_ctx); |
| } |
| mutex_unlock(&aoc_service_lock); |
| } |
| |
| #ifdef AOC_JUNO |
| static irqreturn_t aoc_int_handler(int irq, void *dev) |
| { |
| aoc_clear_gpio_interrupt(); |
| |
| /* Transitioning from offline to online */ |
| if (aoc_state == AOC_STATE_FIRMWARE_LOADED) { |
| if (aoc_fw_ready()) |
| aoc_state = AOC_STATE_STARTING; |
| schedule_work(&aoc_online_work); |
| } |
| } else if (aoc_state == AOC_STATE_ONLINE) { |
| aoc_process_services(dev_get_drvdata(dev), 0); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| #else |
| static irqreturn_t watchdog_int_handler(int irq, void *dev) |
| { |
| struct aoc_prvdata *prvdata = dev_get_drvdata(dev); |
| |
| /* AP shouldn't access AoC registers to clear the IRQ. */ |
| /* Mask the IRQ until the IRQ gets cleared by AoC reset during SSR. */ |
| disable_irq_nosync(irq); |
| schedule_work(&prvdata->watchdog_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| 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; |
| 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]; |
| char ap_reset_reason[RAMDUMP_SECTION_CRASH_INFO_SIZE]; |
| int restart_rc; |
| u32 section_flags; |
| bool ap_reset = false; |
| |
| prvdata->total_restarts++; |
| |
| 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) { |
| prvdata->ap_triggered_reset = false; |
| ap_reset = true; |
| |
| snprintf(ap_reset_reason, RAMDUMP_SECTION_CRASH_INFO_SIZE - 1, |
| "AP Reset: %s", prvdata->ap_reset_reason); |
| |
| trigger_aoc_ramdump(prvdata); |
| } |
| |
| ramdump_timeout = jiffies + (5 * HZ); |
| while (time_before(jiffies, ramdump_timeout)) { |
| if (ramdump_header->valid) |
| break; |
| msleep(100); |
| } |
| |
| if (!ramdump_header->valid) { |
| const char *crash_reason = (const char *)ramdump_header + |
| RAMDUMP_SECTION_CRASH_INFO_OFFSET; |
| bool crash_reason_valid = (strnlen(crash_reason, |
| RAMDUMP_SECTION_CRASH_INFO_SIZE) != 0); |
| |
| dev_err(prvdata->dev, "aoc coredump timed out, coredump only contains DRAM\n"); |
| snprintf(crash_info, RAMDUMP_SECTION_CRASH_INFO_SIZE, |
| "AoC watchdog : %s (incomplete %u:%u)", |
| crash_reason_valid ? crash_reason : "unknown reason", |
| ramdump_header->breadcrumbs[0], ramdump_header->breadcrumbs[1]); |
| } |
| |
| if (ramdump_header->valid && memcmp(ramdump_header, RAMDUMP_MAGIC, sizeof(RAMDUMP_MAGIC))) { |
| dev_err(prvdata->dev, |
| "aoc coredump failed: invalid magic (corruption or incompatible firmware?)\n"); |
| strscpy(crash_info, "AoC Watchdog : coredump corrupt", |
| RAMDUMP_SECTION_CRASH_INFO_SIZE); |
| } |
| |
| 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; |
| } |
| |
| if (ramdump_header->valid) { |
| const char *crash_reason = (const char *)ramdump_header + |
| RAMDUMP_SECTION_CRASH_INFO_OFFSET; |
| |
| section_flags = ramdump_header->sections[RAMDUMP_SECTION_CRASH_INFO_INDEX].flags; |
| if (section_flags & RAMDUMP_FLAG_VALID) |
| strscpy(crash_info, crash_reason, RAMDUMP_SECTION_CRASH_INFO_SIZE); |
| else |
| strscpy(crash_info, "AoC Watchdog : invalid crash info", |
| RAMDUMP_SECTION_CRASH_INFO_SIZE); |
| } |
| |
| if (ap_reset) { |
| /* Prefer the user specified reason */ |
| snprintf(crash_info, RAMDUMP_SECTION_CRASH_INFO_SIZE - 1, "%s", ap_reset_reason); |
| } |
| |
| /* TODO(siqilin): Get paddr and vaddr base from firmware instead */ |
| carveout_paddr_from_aoc = 0x98000000; |
| carveout_vaddr_from_aoc = 0x78000000; |
| /* Entire AoC DRAM carveout, coredump is stored within the carveout */ |
| sscd_info.segs[0].addr = dram_cached; |
| 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: |
| 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); |
| 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); |
| #endif |
| |
| static struct dma_heap *aoc_create_dma_buf_heap(struct aoc_prvdata *prvdata, const char *name, |
| phys_addr_t base, size_t size) |
| { |
| struct device *dev = prvdata->dev; |
| size_t align = SZ_16K; |
| struct dma_heap *heap; |
| |
| heap = ion_physical_heap_create(base, size, align, name, aoc_pheap_alloc_cb, |
| aoc_pheap_free_cb, dev); |
| if (IS_ERR(heap)) |
| dev_err(dev, "heap \"%s\" creation failure: %ld\n", name, PTR_ERR(heap)); |
| |
| return heap; |
| } |
| |
| static bool aoc_create_dma_buf_heaps(struct aoc_prvdata *prvdata) |
| { |
| phys_addr_t base = prvdata->dram_resource.start + resource_size(&prvdata->dram_resource); |
| |
| base -= SENSOR_DIRECT_HEAP_SIZE; |
| prvdata->sensor_heap = aoc_create_dma_buf_heap(prvdata, "sensor_direct_heap", |
| base, SENSOR_DIRECT_HEAP_SIZE); |
| prvdata->sensor_heap_base = base; |
| if (IS_ERR(prvdata->sensor_heap)) |
| return false; |
| |
| base -= PLAYBACK_HEAP_SIZE; |
| prvdata->audio_playback_heap = aoc_create_dma_buf_heap(prvdata, "aaudio_playback_heap", |
| base, PLAYBACK_HEAP_SIZE); |
| prvdata->audio_playback_heap_base = base; |
| if (IS_ERR(prvdata->audio_playback_heap)) |
| return false; |
| |
| base -= CAPTURE_HEAP_SIZE; |
| prvdata->audio_capture_heap = aoc_create_dma_buf_heap(prvdata, "aaudio_capture_heap", |
| base, CAPTURE_HEAP_SIZE); |
| prvdata->audio_capture_heap_base = base; |
| if (IS_ERR(prvdata->audio_capture_heap)) |
| return false; |
| |
| return true; |
| } |
| |
| 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 dma_buf *dmabuf; |
| struct aoc_prvdata *prvdata = file->private_data; |
| struct samsung_dma_buffer *dma_heap_buf; |
| long ret = -EINVAL; |
| |
| switch (cmd) { |
| case AOC_IOCTL_ION_FD_TO_HANDLE: |
| { |
| struct aoc_ion_handle handle; |
| |
| BUILD_BUG_ON(sizeof(struct aoc_ion_handle) != |
| _IOC_SIZE(AOC_IOCTL_ION_FD_TO_HANDLE)); |
| |
| if (copy_from_user(&handle, (struct aoc_ion_handle *)arg, _IOC_SIZE(cmd))) |
| break; |
| |
| dmabuf = dma_buf_get(handle.fd); |
| if (IS_ERR(dmabuf)) { |
| pr_err("fd is not an ion buffer\n"); |
| ret = PTR_ERR(dmabuf); |
| break; |
| } |
| |
| dma_heap_buf = dmabuf->priv; |
| handle.handle = (u64)dma_heap_buf->priv; |
| |
| dma_buf_put(dmabuf); |
| |
| if (!copy_to_user((struct aoc_ion_handle *)arg, &handle, _IOC_SIZE(cmd))) |
| ret = 0; |
| } |
| 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; |
| } |
| |
| #ifdef AOC_JUNO |
| free_irq(aoc_irq, prvdata->dev); |
| aoc_irq = -1; |
| #endif |
| } |
| |
| } |
| |
| static void release_gsa_device(void *prv) |
| { |
| struct aoc_prvdata *prvdata = prv; |
| |
| put_device(prvdata->gsa_dev); |
| } |
| |
| 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, |
| prvdata); |
| } |
| |
| 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 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; |
| unsigned int acpm_async_size; |
| 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_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; |
| } |
| |
| prvdata->dev = dev; |
| prvdata->disable_monitor_mode = 0; |
| prvdata->enable_uart_tx = 0; |
| prvdata->force_voltage_nominal = 0; |
| prvdata->no_ap_resets = 0; |
| |
| rc = find_gsa_device(prvdata); |
| if (rc) { |
| dev_err(dev, "Failed to initialize gsa device: %d\n", rc); |
| } |
| |
| 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; |
| } |
| |
| #ifdef AOC_JUNO |
| aoc_irq = platform_get_irq(pdev, 0); |
| if (aoc_irq < 1) { |
| dev_err(dev, "failed to configure aoc interrupt\n"); |
| rc = aoc_irq; |
| goto err_get_irq; |
| } |
| #else |
| for (i = 0; i < ARRAY_SIZE(prvdata->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)); |
| |
| platform_set_drvdata(pdev, prvdata); |
| |
| ret = allocate_mailbox_channels(prvdata); |
| if (ret) { |
| dev_err(dev, "failed to allocate mailbox channels %d\n", ret); |
| rc = -ENOMEM; |
| goto err_mem_resources; |
| } |
| |
| init_waitqueue_head(&prvdata->aoc_reset_wait_queue); |
| INIT_WORK(&prvdata->watchdog_work, aoc_watchdog); |
| |
| prvdata->watchdog_irq = platform_get_irq_byname(pdev, "watchdog"); |
| if (prvdata->watchdog_irq < 0) { |
| dev_err(dev, "failed to find watchdog irq\n"); |
| rc = -EIO; |
| goto err_watchdog_irq_get; |
| } |
| |
| irq_set_status_flags(prvdata->watchdog_irq, IRQ_NOAUTOEN); |
| ret = devm_request_irq(dev, prvdata->watchdog_irq, watchdog_int_handler, |
| IRQF_TRIGGER_HIGH, dev_name(dev), dev); |
| if (ret != 0) { |
| dev_err(dev, "failed to register watchdog irq handler: %d\n", |
| ret); |
| rc = -EIO; |
| goto err_watchdog_irq_req; |
| } |
| |
| 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 = of_irq_get(sysmmu_node, 0); |
| if (ret < 0) { |
| dev_err(dev, "failed to find sysmmu non-secure irq: %d\n", ret); |
| rc = ret; |
| goto err_watchdog_sysmmu_irq; |
| } |
| prvdata->sysmmu_nonsecure_irq = ret; |
| ret = of_irq_get(sysmmu_node, 1); |
| if (ret < 0) { |
| dev_err(dev, "failed to find sysmmu secure irq: %d\n", ret); |
| rc = ret; |
| goto err_watchdog_sysmmu_irq; |
| } |
| prvdata->sysmmu_secure_irq = ret; |
| of_node_put(sysmmu_node); |
| #endif |
| |
| pr_notice("found aoc with interrupt:%d sram:%pR dram:%pR\n", aoc_irq, |
| aoc_sram_resource, &prvdata->dram_resource); |
| aoc_platform_device = pdev; |
| |
| 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; |
| } |
| |
| #ifndef AOC_JUNO |
| 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); |
| |
| aoc_configure_sysmmu(prvdata); |
| |
| if (!aoc_create_dma_buf_heaps(prvdata)) { |
| pr_err("Unable to create dma_buf heaps\n"); |
| aoc_cleanup_resources(pdev); |
| return -ENOMEM; |
| } |
| #endif |
| |
| prvdata->sensor_power_count = of_property_count_strings(dev->of_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(dev->of_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); |
| |
| /* 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_interrupt(); |
| |
| #ifdef AOC_JUNO |
| ret = request_irq(aoc_irq, aoc_int_handler, IRQF_TRIGGER_HIGH, "aoc", |
| prvdata->_device); |
| if (ret != 0) { |
| pr_err("failed to register interrupt handler : %d\n", ret); |
| |
| rc = -ENXIO; |
| goto err_aoc_irq_req; |
| } |
| #endif |
| |
| ret = acpm_ipc_request_channel(aoc_node, acpm_aoc_reset_callback, |
| &prvdata->acpm_async_id, &acpm_async_size); |
| if (ret < 0) { |
| dev_err(dev, "failed to register acpm aoc reset callback\n"); |
| rc = -EIO; |
| /* goto err_acmp_reset; */ |
| } |
| |
| #if IS_ENABLED(CONFIG_EXYNOS_ITMON) |
| prvdata->itmon_nb.notifier_call = aoc_itmon_notifier; |
| itmon_notifier_chain_register(&prvdata->itmon_nb); |
| #endif |
| |
| 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: */ |
| #ifdef AOC_JUNO |
| err_aoc_irq_req: |
| #endif |
| #ifndef AOC_JUNO |
| err_find_iommu: |
| err_s2mpu_map: |
| #endif |
| err_sram_dram_map: |
| |
| #ifndef AOC_JUNO |
| err_watchdog_sysmmu_irq: |
| err_watchdog_irq_req: |
| err_watchdog_irq_get: |
| #else |
| err_get_irq: |
| #endif |
| err_mem_resources: |
| aoc_cleanup_resources(pdev); |
| err_memnode: |
| deinit_chardev(prvdata); |
| err_chardev: |
| kfree(prvdata); |
| err_failed_prvdata_alloc: |
| 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); |
| |
| 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"); |