12159ad93SMark Brown /* 22159ad93SMark Brown * wm_adsp.c -- Wolfson ADSP support 32159ad93SMark Brown * 42159ad93SMark Brown * Copyright 2012 Wolfson Microelectronics plc 52159ad93SMark Brown * 62159ad93SMark Brown * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 72159ad93SMark Brown * 82159ad93SMark Brown * This program is free software; you can redistribute it and/or modify 92159ad93SMark Brown * it under the terms of the GNU General Public License version 2 as 102159ad93SMark Brown * published by the Free Software Foundation. 112159ad93SMark Brown */ 122159ad93SMark Brown 132159ad93SMark Brown #include <linux/module.h> 142159ad93SMark Brown #include <linux/moduleparam.h> 152159ad93SMark Brown #include <linux/init.h> 162159ad93SMark Brown #include <linux/delay.h> 172159ad93SMark Brown #include <linux/firmware.h> 182159ad93SMark Brown #include <linux/pm.h> 192159ad93SMark Brown #include <linux/pm_runtime.h> 202159ad93SMark Brown #include <linux/regmap.h> 21973838a0SMark Brown #include <linux/regulator/consumer.h> 222159ad93SMark Brown #include <linux/slab.h> 232159ad93SMark Brown #include <sound/core.h> 242159ad93SMark Brown #include <sound/pcm.h> 252159ad93SMark Brown #include <sound/pcm_params.h> 262159ad93SMark Brown #include <sound/soc.h> 272159ad93SMark Brown #include <sound/jack.h> 282159ad93SMark Brown #include <sound/initval.h> 292159ad93SMark Brown #include <sound/tlv.h> 302159ad93SMark Brown 312159ad93SMark Brown #include <linux/mfd/arizona/registers.h> 322159ad93SMark Brown 332159ad93SMark Brown #include "wm_adsp.h" 342159ad93SMark Brown 352159ad93SMark Brown #define adsp_crit(_dsp, fmt, ...) \ 362159ad93SMark Brown dev_crit(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) 372159ad93SMark Brown #define adsp_err(_dsp, fmt, ...) \ 382159ad93SMark Brown dev_err(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) 392159ad93SMark Brown #define adsp_warn(_dsp, fmt, ...) \ 402159ad93SMark Brown dev_warn(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) 412159ad93SMark Brown #define adsp_info(_dsp, fmt, ...) \ 422159ad93SMark Brown dev_info(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) 432159ad93SMark Brown #define adsp_dbg(_dsp, fmt, ...) \ 442159ad93SMark Brown dev_dbg(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) 452159ad93SMark Brown 462159ad93SMark Brown #define ADSP1_CONTROL_1 0x00 472159ad93SMark Brown #define ADSP1_CONTROL_2 0x02 482159ad93SMark Brown #define ADSP1_CONTROL_3 0x03 492159ad93SMark Brown #define ADSP1_CONTROL_4 0x04 502159ad93SMark Brown #define ADSP1_CONTROL_5 0x06 512159ad93SMark Brown #define ADSP1_CONTROL_6 0x07 522159ad93SMark Brown #define ADSP1_CONTROL_7 0x08 532159ad93SMark Brown #define ADSP1_CONTROL_8 0x09 542159ad93SMark Brown #define ADSP1_CONTROL_9 0x0A 552159ad93SMark Brown #define ADSP1_CONTROL_10 0x0B 562159ad93SMark Brown #define ADSP1_CONTROL_11 0x0C 572159ad93SMark Brown #define ADSP1_CONTROL_12 0x0D 582159ad93SMark Brown #define ADSP1_CONTROL_13 0x0F 592159ad93SMark Brown #define ADSP1_CONTROL_14 0x10 602159ad93SMark Brown #define ADSP1_CONTROL_15 0x11 612159ad93SMark Brown #define ADSP1_CONTROL_16 0x12 622159ad93SMark Brown #define ADSP1_CONTROL_17 0x13 632159ad93SMark Brown #define ADSP1_CONTROL_18 0x14 642159ad93SMark Brown #define ADSP1_CONTROL_19 0x16 652159ad93SMark Brown #define ADSP1_CONTROL_20 0x17 662159ad93SMark Brown #define ADSP1_CONTROL_21 0x18 672159ad93SMark Brown #define ADSP1_CONTROL_22 0x1A 682159ad93SMark Brown #define ADSP1_CONTROL_23 0x1B 692159ad93SMark Brown #define ADSP1_CONTROL_24 0x1C 702159ad93SMark Brown #define ADSP1_CONTROL_25 0x1E 712159ad93SMark Brown #define ADSP1_CONTROL_26 0x20 722159ad93SMark Brown #define ADSP1_CONTROL_27 0x21 732159ad93SMark Brown #define ADSP1_CONTROL_28 0x22 742159ad93SMark Brown #define ADSP1_CONTROL_29 0x23 752159ad93SMark Brown #define ADSP1_CONTROL_30 0x24 762159ad93SMark Brown #define ADSP1_CONTROL_31 0x26 772159ad93SMark Brown 782159ad93SMark Brown /* 792159ad93SMark Brown * ADSP1 Control 19 802159ad93SMark Brown */ 812159ad93SMark Brown #define ADSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ 822159ad93SMark Brown #define ADSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ 832159ad93SMark Brown #define ADSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ 842159ad93SMark Brown 852159ad93SMark Brown 862159ad93SMark Brown /* 872159ad93SMark Brown * ADSP1 Control 30 882159ad93SMark Brown */ 892159ad93SMark Brown #define ADSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ 902159ad93SMark Brown #define ADSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ 912159ad93SMark Brown #define ADSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ 922159ad93SMark Brown #define ADSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ 932159ad93SMark Brown #define ADSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ 942159ad93SMark Brown #define ADSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ 952159ad93SMark Brown #define ADSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ 962159ad93SMark Brown #define ADSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ 972159ad93SMark Brown #define ADSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ 982159ad93SMark Brown #define ADSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ 992159ad93SMark Brown #define ADSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ 1002159ad93SMark Brown #define ADSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ 1012159ad93SMark Brown #define ADSP1_START 0x0001 /* DSP1_START */ 1022159ad93SMark Brown #define ADSP1_START_MASK 0x0001 /* DSP1_START */ 1032159ad93SMark Brown #define ADSP1_START_SHIFT 0 /* DSP1_START */ 1042159ad93SMark Brown #define ADSP1_START_WIDTH 1 /* DSP1_START */ 1052159ad93SMark Brown 1062159ad93SMark Brown #define ADSP2_CONTROL 0 107973838a0SMark Brown #define ADSP2_CLOCKING 1 1082159ad93SMark Brown #define ADSP2_STATUS1 4 1092159ad93SMark Brown 1102159ad93SMark Brown /* 1112159ad93SMark Brown * ADSP2 Control 1122159ad93SMark Brown */ 1132159ad93SMark Brown 1142159ad93SMark Brown #define ADSP2_MEM_ENA 0x0010 /* DSP1_MEM_ENA */ 1152159ad93SMark Brown #define ADSP2_MEM_ENA_MASK 0x0010 /* DSP1_MEM_ENA */ 1162159ad93SMark Brown #define ADSP2_MEM_ENA_SHIFT 4 /* DSP1_MEM_ENA */ 1172159ad93SMark Brown #define ADSP2_MEM_ENA_WIDTH 1 /* DSP1_MEM_ENA */ 1182159ad93SMark Brown #define ADSP2_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ 1192159ad93SMark Brown #define ADSP2_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ 1202159ad93SMark Brown #define ADSP2_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ 1212159ad93SMark Brown #define ADSP2_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ 1222159ad93SMark Brown #define ADSP2_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ 1232159ad93SMark Brown #define ADSP2_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ 1242159ad93SMark Brown #define ADSP2_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ 1252159ad93SMark Brown #define ADSP2_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ 1262159ad93SMark Brown #define ADSP2_START 0x0001 /* DSP1_START */ 1272159ad93SMark Brown #define ADSP2_START_MASK 0x0001 /* DSP1_START */ 1282159ad93SMark Brown #define ADSP2_START_SHIFT 0 /* DSP1_START */ 1292159ad93SMark Brown #define ADSP2_START_WIDTH 1 /* DSP1_START */ 1302159ad93SMark Brown 1312159ad93SMark Brown /* 132973838a0SMark Brown * ADSP2 clocking 133973838a0SMark Brown */ 134973838a0SMark Brown #define ADSP2_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ 135973838a0SMark Brown #define ADSP2_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ 136973838a0SMark Brown #define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ 137973838a0SMark Brown 138973838a0SMark Brown /* 1392159ad93SMark Brown * ADSP2 Status 1 1402159ad93SMark Brown */ 1412159ad93SMark Brown #define ADSP2_RAM_RDY 0x0001 1422159ad93SMark Brown #define ADSP2_RAM_RDY_MASK 0x0001 1432159ad93SMark Brown #define ADSP2_RAM_RDY_SHIFT 0 1442159ad93SMark Brown #define ADSP2_RAM_RDY_WIDTH 1 1452159ad93SMark Brown 1462159ad93SMark Brown 1472159ad93SMark Brown static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp, 1482159ad93SMark Brown int type) 1492159ad93SMark Brown { 1502159ad93SMark Brown int i; 1512159ad93SMark Brown 1522159ad93SMark Brown for (i = 0; i < dsp->num_mems; i++) 1532159ad93SMark Brown if (dsp->mem[i].type == type) 1542159ad93SMark Brown return &dsp->mem[i]; 1552159ad93SMark Brown 1562159ad93SMark Brown return NULL; 1572159ad93SMark Brown } 1582159ad93SMark Brown 1592159ad93SMark Brown static int wm_adsp_load(struct wm_adsp *dsp) 1602159ad93SMark Brown { 1612159ad93SMark Brown const struct firmware *firmware; 1622159ad93SMark Brown struct regmap *regmap = dsp->regmap; 1632159ad93SMark Brown unsigned int pos = 0; 1642159ad93SMark Brown const struct wmfw_header *header; 1652159ad93SMark Brown const struct wmfw_adsp1_sizes *adsp1_sizes; 1662159ad93SMark Brown const struct wmfw_adsp2_sizes *adsp2_sizes; 1672159ad93SMark Brown const struct wmfw_footer *footer; 1682159ad93SMark Brown const struct wmfw_region *region; 1692159ad93SMark Brown const struct wm_adsp_region *mem; 1702159ad93SMark Brown const char *region_name; 1712159ad93SMark Brown char *file, *text; 172a76fefabSMark Brown void *buf; 1732159ad93SMark Brown unsigned int reg; 1742159ad93SMark Brown int regions = 0; 1752159ad93SMark Brown int ret, offset, type, sizes; 1762159ad93SMark Brown 1772159ad93SMark Brown file = kzalloc(PAGE_SIZE, GFP_KERNEL); 1782159ad93SMark Brown if (file == NULL) 1792159ad93SMark Brown return -ENOMEM; 1802159ad93SMark Brown 1812159ad93SMark Brown snprintf(file, PAGE_SIZE, "%s-dsp%d.wmfw", dsp->part, dsp->num); 1822159ad93SMark Brown file[PAGE_SIZE - 1] = '\0'; 1832159ad93SMark Brown 1842159ad93SMark Brown ret = request_firmware(&firmware, file, dsp->dev); 1852159ad93SMark Brown if (ret != 0) { 1862159ad93SMark Brown adsp_err(dsp, "Failed to request '%s'\n", file); 1872159ad93SMark Brown goto out; 1882159ad93SMark Brown } 1892159ad93SMark Brown ret = -EINVAL; 1902159ad93SMark Brown 1912159ad93SMark Brown pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); 1922159ad93SMark Brown if (pos >= firmware->size) { 1932159ad93SMark Brown adsp_err(dsp, "%s: file too short, %zu bytes\n", 1942159ad93SMark Brown file, firmware->size); 1952159ad93SMark Brown goto out_fw; 1962159ad93SMark Brown } 1972159ad93SMark Brown 1982159ad93SMark Brown header = (void*)&firmware->data[0]; 1992159ad93SMark Brown 2002159ad93SMark Brown if (memcmp(&header->magic[0], "WMFW", 4) != 0) { 2012159ad93SMark Brown adsp_err(dsp, "%s: invalid magic\n", file); 2022159ad93SMark Brown goto out_fw; 2032159ad93SMark Brown } 2042159ad93SMark Brown 2052159ad93SMark Brown if (header->ver != 0) { 2062159ad93SMark Brown adsp_err(dsp, "%s: unknown file format %d\n", 2072159ad93SMark Brown file, header->ver); 2082159ad93SMark Brown goto out_fw; 2092159ad93SMark Brown } 2102159ad93SMark Brown 2112159ad93SMark Brown if (header->core != dsp->type) { 2122159ad93SMark Brown adsp_err(dsp, "%s: invalid core %d != %d\n", 2132159ad93SMark Brown file, header->core, dsp->type); 2142159ad93SMark Brown goto out_fw; 2152159ad93SMark Brown } 2162159ad93SMark Brown 2172159ad93SMark Brown switch (dsp->type) { 2182159ad93SMark Brown case WMFW_ADSP1: 2192159ad93SMark Brown pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); 2202159ad93SMark Brown adsp1_sizes = (void *)&(header[1]); 2212159ad93SMark Brown footer = (void *)&(adsp1_sizes[1]); 2222159ad93SMark Brown sizes = sizeof(*adsp1_sizes); 2232159ad93SMark Brown 2242159ad93SMark Brown adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", 2252159ad93SMark Brown file, le32_to_cpu(adsp1_sizes->dm), 2262159ad93SMark Brown le32_to_cpu(adsp1_sizes->pm), 2272159ad93SMark Brown le32_to_cpu(adsp1_sizes->zm)); 2282159ad93SMark Brown break; 2292159ad93SMark Brown 2302159ad93SMark Brown case WMFW_ADSP2: 2312159ad93SMark Brown pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer); 2322159ad93SMark Brown adsp2_sizes = (void *)&(header[1]); 2332159ad93SMark Brown footer = (void *)&(adsp2_sizes[1]); 2342159ad93SMark Brown sizes = sizeof(*adsp2_sizes); 2352159ad93SMark Brown 2362159ad93SMark Brown adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", 2372159ad93SMark Brown file, le32_to_cpu(adsp2_sizes->xm), 2382159ad93SMark Brown le32_to_cpu(adsp2_sizes->ym), 2392159ad93SMark Brown le32_to_cpu(adsp2_sizes->pm), 2402159ad93SMark Brown le32_to_cpu(adsp2_sizes->zm)); 2412159ad93SMark Brown break; 2422159ad93SMark Brown 2432159ad93SMark Brown default: 2442159ad93SMark Brown BUG_ON(NULL == "Unknown DSP type"); 2452159ad93SMark Brown goto out_fw; 2462159ad93SMark Brown } 2472159ad93SMark Brown 2482159ad93SMark Brown if (le32_to_cpu(header->len) != sizeof(*header) + 2492159ad93SMark Brown sizes + sizeof(*footer)) { 2502159ad93SMark Brown adsp_err(dsp, "%s: unexpected header length %d\n", 2512159ad93SMark Brown file, le32_to_cpu(header->len)); 2522159ad93SMark Brown goto out_fw; 2532159ad93SMark Brown } 2542159ad93SMark Brown 2552159ad93SMark Brown adsp_dbg(dsp, "%s: timestamp %llu\n", file, 2562159ad93SMark Brown le64_to_cpu(footer->timestamp)); 2572159ad93SMark Brown 2582159ad93SMark Brown while (pos < firmware->size && 2592159ad93SMark Brown pos - firmware->size > sizeof(*region)) { 2602159ad93SMark Brown region = (void *)&(firmware->data[pos]); 2612159ad93SMark Brown region_name = "Unknown"; 2622159ad93SMark Brown reg = 0; 2632159ad93SMark Brown text = NULL; 2642159ad93SMark Brown offset = le32_to_cpu(region->offset) & 0xffffff; 2652159ad93SMark Brown type = be32_to_cpu(region->type) & 0xff; 2662159ad93SMark Brown mem = wm_adsp_find_region(dsp, type); 2672159ad93SMark Brown 2682159ad93SMark Brown switch (type) { 2692159ad93SMark Brown case WMFW_NAME_TEXT: 2702159ad93SMark Brown region_name = "Firmware name"; 2712159ad93SMark Brown text = kzalloc(le32_to_cpu(region->len) + 1, 2722159ad93SMark Brown GFP_KERNEL); 2732159ad93SMark Brown break; 2742159ad93SMark Brown case WMFW_INFO_TEXT: 2752159ad93SMark Brown region_name = "Information"; 2762159ad93SMark Brown text = kzalloc(le32_to_cpu(region->len) + 1, 2772159ad93SMark Brown GFP_KERNEL); 2782159ad93SMark Brown break; 2792159ad93SMark Brown case WMFW_ABSOLUTE: 2802159ad93SMark Brown region_name = "Absolute"; 2812159ad93SMark Brown reg = offset; 2822159ad93SMark Brown break; 2832159ad93SMark Brown case WMFW_ADSP1_PM: 2842159ad93SMark Brown BUG_ON(!mem); 2852159ad93SMark Brown region_name = "PM"; 2862159ad93SMark Brown reg = mem->base + (offset * 3); 2872159ad93SMark Brown break; 2882159ad93SMark Brown case WMFW_ADSP1_DM: 2892159ad93SMark Brown BUG_ON(!mem); 2902159ad93SMark Brown region_name = "DM"; 2912159ad93SMark Brown reg = mem->base + (offset * 2); 2922159ad93SMark Brown break; 2932159ad93SMark Brown case WMFW_ADSP2_XM: 2942159ad93SMark Brown BUG_ON(!mem); 2952159ad93SMark Brown region_name = "XM"; 2962159ad93SMark Brown reg = mem->base + (offset * 2); 2972159ad93SMark Brown break; 2982159ad93SMark Brown case WMFW_ADSP2_YM: 2992159ad93SMark Brown BUG_ON(!mem); 3002159ad93SMark Brown region_name = "YM"; 3012159ad93SMark Brown reg = mem->base + (offset * 2); 3022159ad93SMark Brown break; 3032159ad93SMark Brown case WMFW_ADSP1_ZM: 3042159ad93SMark Brown BUG_ON(!mem); 3052159ad93SMark Brown region_name = "ZM"; 3062159ad93SMark Brown reg = mem->base + (offset * 2); 3072159ad93SMark Brown break; 3082159ad93SMark Brown default: 3092159ad93SMark Brown adsp_warn(dsp, 3102159ad93SMark Brown "%s.%d: Unknown region type %x at %d(%x)\n", 3112159ad93SMark Brown file, regions, type, pos, pos); 3122159ad93SMark Brown break; 3132159ad93SMark Brown } 3142159ad93SMark Brown 3152159ad93SMark Brown adsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file, 3162159ad93SMark Brown regions, le32_to_cpu(region->len), offset, 3172159ad93SMark Brown region_name); 3182159ad93SMark Brown 3192159ad93SMark Brown if (text) { 3202159ad93SMark Brown memcpy(text, region->data, le32_to_cpu(region->len)); 3212159ad93SMark Brown adsp_info(dsp, "%s: %s\n", file, text); 3222159ad93SMark Brown kfree(text); 3232159ad93SMark Brown } 3242159ad93SMark Brown 3252159ad93SMark Brown if (reg) { 326a76fefabSMark Brown buf = kmemdup(region->data, le32_to_cpu(region->len), 327a76fefabSMark Brown GFP_KERNEL); 328a76fefabSMark Brown if (!buf) { 329a76fefabSMark Brown adsp_err(dsp, "Out of memory\n"); 330a76fefabSMark Brown return -ENOMEM; 331a76fefabSMark Brown } 332a76fefabSMark Brown 333a76fefabSMark Brown ret = regmap_raw_write(regmap, reg, buf, 3342159ad93SMark Brown le32_to_cpu(region->len)); 335a76fefabSMark Brown 336a76fefabSMark Brown kfree(buf); 337a76fefabSMark Brown 3382159ad93SMark Brown if (ret != 0) { 3392159ad93SMark Brown adsp_err(dsp, 3402159ad93SMark Brown "%s.%d: Failed to write %d bytes at %d in %s: %d\n", 3412159ad93SMark Brown file, regions, 3422159ad93SMark Brown le32_to_cpu(region->len), offset, 3432159ad93SMark Brown region_name, ret); 3442159ad93SMark Brown goto out_fw; 3452159ad93SMark Brown } 3462159ad93SMark Brown } 3472159ad93SMark Brown 3482159ad93SMark Brown pos += le32_to_cpu(region->len) + sizeof(*region); 3492159ad93SMark Brown regions++; 3502159ad93SMark Brown } 3512159ad93SMark Brown 3522159ad93SMark Brown if (pos > firmware->size) 3532159ad93SMark Brown adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", 3542159ad93SMark Brown file, regions, pos - firmware->size); 3552159ad93SMark Brown 3562159ad93SMark Brown out_fw: 3572159ad93SMark Brown release_firmware(firmware); 3582159ad93SMark Brown out: 3592159ad93SMark Brown kfree(file); 3602159ad93SMark Brown 3612159ad93SMark Brown return ret; 3622159ad93SMark Brown } 3632159ad93SMark Brown 3642159ad93SMark Brown static int wm_adsp_load_coeff(struct wm_adsp *dsp) 3652159ad93SMark Brown { 3662159ad93SMark Brown struct regmap *regmap = dsp->regmap; 3672159ad93SMark Brown struct wmfw_coeff_hdr *hdr; 3682159ad93SMark Brown struct wmfw_coeff_item *blk; 3692159ad93SMark Brown const struct firmware *firmware; 3702159ad93SMark Brown const char *region_name; 3712159ad93SMark Brown int ret, pos, blocks, type, offset, reg; 3722159ad93SMark Brown char *file; 373a76fefabSMark Brown void *buf; 3742159ad93SMark Brown 3752159ad93SMark Brown file = kzalloc(PAGE_SIZE, GFP_KERNEL); 3762159ad93SMark Brown if (file == NULL) 3772159ad93SMark Brown return -ENOMEM; 3782159ad93SMark Brown 3792159ad93SMark Brown snprintf(file, PAGE_SIZE, "%s-dsp%d.bin", dsp->part, dsp->num); 3802159ad93SMark Brown file[PAGE_SIZE - 1] = '\0'; 3812159ad93SMark Brown 3822159ad93SMark Brown ret = request_firmware(&firmware, file, dsp->dev); 3832159ad93SMark Brown if (ret != 0) { 3842159ad93SMark Brown adsp_warn(dsp, "Failed to request '%s'\n", file); 3852159ad93SMark Brown ret = 0; 3862159ad93SMark Brown goto out; 3872159ad93SMark Brown } 3882159ad93SMark Brown ret = -EINVAL; 3892159ad93SMark Brown 3902159ad93SMark Brown if (sizeof(*hdr) >= firmware->size) { 3912159ad93SMark Brown adsp_err(dsp, "%s: file too short, %zu bytes\n", 3922159ad93SMark Brown file, firmware->size); 3932159ad93SMark Brown goto out_fw; 3942159ad93SMark Brown } 3952159ad93SMark Brown 3962159ad93SMark Brown hdr = (void*)&firmware->data[0]; 3972159ad93SMark Brown if (memcmp(hdr->magic, "WMDR", 4) != 0) { 3982159ad93SMark Brown adsp_err(dsp, "%s: invalid magic\n", file); 3992159ad93SMark Brown return -EINVAL; 4002159ad93SMark Brown } 4012159ad93SMark Brown 4022159ad93SMark Brown adsp_dbg(dsp, "%s: v%d.%d.%d\n", file, 4032159ad93SMark Brown (le32_to_cpu(hdr->ver) >> 16) & 0xff, 4042159ad93SMark Brown (le32_to_cpu(hdr->ver) >> 8) & 0xff, 4052159ad93SMark Brown le32_to_cpu(hdr->ver) & 0xff); 4062159ad93SMark Brown 4072159ad93SMark Brown pos = le32_to_cpu(hdr->len); 4082159ad93SMark Brown 4092159ad93SMark Brown blocks = 0; 4102159ad93SMark Brown while (pos < firmware->size && 4112159ad93SMark Brown pos - firmware->size > sizeof(*blk)) { 4122159ad93SMark Brown blk = (void*)(&firmware->data[pos]); 4132159ad93SMark Brown 4142159ad93SMark Brown type = be32_to_cpu(blk->type) & 0xff; 4152159ad93SMark Brown offset = le32_to_cpu(blk->offset) & 0xffffff; 4162159ad93SMark Brown 4172159ad93SMark Brown adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n", 4182159ad93SMark Brown file, blocks, le32_to_cpu(blk->id), 4192159ad93SMark Brown (le32_to_cpu(blk->ver) >> 16) & 0xff, 4202159ad93SMark Brown (le32_to_cpu(blk->ver) >> 8) & 0xff, 4212159ad93SMark Brown le32_to_cpu(blk->ver) & 0xff); 4222159ad93SMark Brown adsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n", 4232159ad93SMark Brown file, blocks, le32_to_cpu(blk->len), offset, type); 4242159ad93SMark Brown 4252159ad93SMark Brown reg = 0; 4262159ad93SMark Brown region_name = "Unknown"; 4272159ad93SMark Brown switch (type) { 4282159ad93SMark Brown case WMFW_NAME_TEXT: 4292159ad93SMark Brown case WMFW_INFO_TEXT: 4302159ad93SMark Brown break; 4312159ad93SMark Brown case WMFW_ABSOLUTE: 4322159ad93SMark Brown region_name = "register"; 4332159ad93SMark Brown reg = offset; 4342159ad93SMark Brown break; 4352159ad93SMark Brown default: 4362159ad93SMark Brown adsp_err(dsp, "Unknown region type %x\n", type); 4372159ad93SMark Brown break; 4382159ad93SMark Brown } 4392159ad93SMark Brown 4402159ad93SMark Brown if (reg) { 441a76fefabSMark Brown buf = kmemdup(blk->data, le32_to_cpu(blk->len), 442a76fefabSMark Brown GFP_KERNEL); 443a76fefabSMark Brown if (!buf) { 444a76fefabSMark Brown adsp_err(dsp, "Out of memory\n"); 445a76fefabSMark Brown return -ENOMEM; 446a76fefabSMark Brown } 447a76fefabSMark Brown 4482159ad93SMark Brown ret = regmap_raw_write(regmap, reg, blk->data, 4492159ad93SMark Brown le32_to_cpu(blk->len)); 4502159ad93SMark Brown if (ret != 0) { 4512159ad93SMark Brown adsp_err(dsp, 4522159ad93SMark Brown "%s.%d: Failed to write to %x in %s\n", 4532159ad93SMark Brown file, blocks, reg, region_name); 4542159ad93SMark Brown } 455a76fefabSMark Brown 456a76fefabSMark Brown kfree(buf); 4572159ad93SMark Brown } 4582159ad93SMark Brown 4592159ad93SMark Brown pos += le32_to_cpu(blk->len) + sizeof(*blk); 4602159ad93SMark Brown blocks++; 4612159ad93SMark Brown } 4622159ad93SMark Brown 4632159ad93SMark Brown if (pos > firmware->size) 4642159ad93SMark Brown adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", 4652159ad93SMark Brown file, blocks, pos - firmware->size); 4662159ad93SMark Brown 4672159ad93SMark Brown out_fw: 4682159ad93SMark Brown release_firmware(firmware); 4692159ad93SMark Brown out: 4702159ad93SMark Brown kfree(file); 4712159ad93SMark Brown return 0; 4722159ad93SMark Brown } 4732159ad93SMark Brown 4742159ad93SMark Brown int wm_adsp1_event(struct snd_soc_dapm_widget *w, 4752159ad93SMark Brown struct snd_kcontrol *kcontrol, 4762159ad93SMark Brown int event) 4772159ad93SMark Brown { 4782159ad93SMark Brown struct snd_soc_codec *codec = w->codec; 4792159ad93SMark Brown struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec); 4802159ad93SMark Brown struct wm_adsp *dsp = &dsps[w->shift]; 4812159ad93SMark Brown int ret; 4822159ad93SMark Brown 4832159ad93SMark Brown switch (event) { 4842159ad93SMark Brown case SND_SOC_DAPM_POST_PMU: 4852159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, 4862159ad93SMark Brown ADSP1_SYS_ENA, ADSP1_SYS_ENA); 4872159ad93SMark Brown 4882159ad93SMark Brown ret = wm_adsp_load(dsp); 4892159ad93SMark Brown if (ret != 0) 4902159ad93SMark Brown goto err; 4912159ad93SMark Brown 4922159ad93SMark Brown ret = wm_adsp_load_coeff(dsp); 4932159ad93SMark Brown if (ret != 0) 4942159ad93SMark Brown goto err; 4952159ad93SMark Brown 4962159ad93SMark Brown /* Start the core running */ 4972159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, 4982159ad93SMark Brown ADSP1_CORE_ENA | ADSP1_START, 4992159ad93SMark Brown ADSP1_CORE_ENA | ADSP1_START); 5002159ad93SMark Brown break; 5012159ad93SMark Brown 5022159ad93SMark Brown case SND_SOC_DAPM_PRE_PMD: 5032159ad93SMark Brown /* Halt the core */ 5042159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, 5052159ad93SMark Brown ADSP1_CORE_ENA | ADSP1_START, 0); 5062159ad93SMark Brown 5072159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19, 5082159ad93SMark Brown ADSP1_WDMA_BUFFER_LENGTH_MASK, 0); 5092159ad93SMark Brown 5102159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, 5112159ad93SMark Brown ADSP1_SYS_ENA, 0); 5122159ad93SMark Brown break; 5132159ad93SMark Brown 5142159ad93SMark Brown default: 5152159ad93SMark Brown break; 5162159ad93SMark Brown } 5172159ad93SMark Brown 5182159ad93SMark Brown return 0; 5192159ad93SMark Brown 5202159ad93SMark Brown err: 5212159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, 5222159ad93SMark Brown ADSP1_SYS_ENA, 0); 5232159ad93SMark Brown return ret; 5242159ad93SMark Brown } 5252159ad93SMark Brown EXPORT_SYMBOL_GPL(wm_adsp1_event); 5262159ad93SMark Brown 5272159ad93SMark Brown static int wm_adsp2_ena(struct wm_adsp *dsp) 5282159ad93SMark Brown { 5292159ad93SMark Brown unsigned int val; 5302159ad93SMark Brown int ret, count; 5312159ad93SMark Brown 5322159ad93SMark Brown ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, 5332159ad93SMark Brown ADSP2_SYS_ENA, ADSP2_SYS_ENA); 5342159ad93SMark Brown if (ret != 0) 5352159ad93SMark Brown return ret; 5362159ad93SMark Brown 5372159ad93SMark Brown /* Wait for the RAM to start, should be near instantaneous */ 5382159ad93SMark Brown count = 0; 5392159ad93SMark Brown do { 5402159ad93SMark Brown ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, 5412159ad93SMark Brown &val); 5422159ad93SMark Brown if (ret != 0) 5432159ad93SMark Brown return ret; 5442159ad93SMark Brown } while (!(val & ADSP2_RAM_RDY) && ++count < 10); 5452159ad93SMark Brown 5462159ad93SMark Brown if (!(val & ADSP2_RAM_RDY)) { 5472159ad93SMark Brown adsp_err(dsp, "Failed to start DSP RAM\n"); 5482159ad93SMark Brown return -EBUSY; 5492159ad93SMark Brown } 5502159ad93SMark Brown 5512159ad93SMark Brown adsp_dbg(dsp, "RAM ready after %d polls\n", count); 5522159ad93SMark Brown adsp_info(dsp, "RAM ready after %d polls\n", count); 5532159ad93SMark Brown 5542159ad93SMark Brown return 0; 5552159ad93SMark Brown } 5562159ad93SMark Brown 5572159ad93SMark Brown int wm_adsp2_event(struct snd_soc_dapm_widget *w, 5582159ad93SMark Brown struct snd_kcontrol *kcontrol, int event) 5592159ad93SMark Brown { 5602159ad93SMark Brown struct snd_soc_codec *codec = w->codec; 5612159ad93SMark Brown struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec); 5622159ad93SMark Brown struct wm_adsp *dsp = &dsps[w->shift]; 563973838a0SMark Brown unsigned int val; 5642159ad93SMark Brown int ret; 5652159ad93SMark Brown 5662159ad93SMark Brown switch (event) { 5672159ad93SMark Brown case SND_SOC_DAPM_POST_PMU: 568dd49e2c8SMark Brown /* 569dd49e2c8SMark Brown * For simplicity set the DSP clock rate to be the 570dd49e2c8SMark Brown * SYSCLK rate rather than making it configurable. 571dd49e2c8SMark Brown */ 572dd49e2c8SMark Brown ret = regmap_read(dsp->regmap, ARIZONA_SYSTEM_CLOCK_1, &val); 573dd49e2c8SMark Brown if (ret != 0) { 574dd49e2c8SMark Brown adsp_err(dsp, "Failed to read SYSCLK state: %d\n", 575dd49e2c8SMark Brown ret); 576dd49e2c8SMark Brown return ret; 577dd49e2c8SMark Brown } 578dd49e2c8SMark Brown val = (val & ARIZONA_SYSCLK_FREQ_MASK) 579dd49e2c8SMark Brown >> ARIZONA_SYSCLK_FREQ_SHIFT; 580dd49e2c8SMark Brown 581dd49e2c8SMark Brown ret = regmap_update_bits(dsp->regmap, 582dd49e2c8SMark Brown dsp->base + ADSP2_CLOCKING, 583dd49e2c8SMark Brown ADSP2_CLK_SEL_MASK, val); 584dd49e2c8SMark Brown if (ret != 0) { 585dd49e2c8SMark Brown adsp_err(dsp, "Failed to set clock rate: %d\n", 586dd49e2c8SMark Brown ret); 587dd49e2c8SMark Brown return ret; 588dd49e2c8SMark Brown } 589dd49e2c8SMark Brown 590973838a0SMark Brown if (dsp->dvfs) { 591973838a0SMark Brown ret = regmap_read(dsp->regmap, 592973838a0SMark Brown dsp->base + ADSP2_CLOCKING, &val); 593973838a0SMark Brown if (ret != 0) { 594973838a0SMark Brown dev_err(dsp->dev, 595973838a0SMark Brown "Failed to read clocking: %d\n", ret); 596973838a0SMark Brown return ret; 597973838a0SMark Brown } 598973838a0SMark Brown 59925c6fdb0SMark Brown if ((val & ADSP2_CLK_SEL_MASK) >= 3) { 600973838a0SMark Brown ret = regulator_enable(dsp->dvfs); 601973838a0SMark Brown if (ret != 0) { 602973838a0SMark Brown dev_err(dsp->dev, 603973838a0SMark Brown "Failed to enable supply: %d\n", 604973838a0SMark Brown ret); 605973838a0SMark Brown return ret; 606973838a0SMark Brown } 607973838a0SMark Brown 608973838a0SMark Brown ret = regulator_set_voltage(dsp->dvfs, 609973838a0SMark Brown 1800000, 610973838a0SMark Brown 1800000); 611973838a0SMark Brown if (ret != 0) { 612973838a0SMark Brown dev_err(dsp->dev, 613973838a0SMark Brown "Failed to raise supply: %d\n", 614973838a0SMark Brown ret); 615973838a0SMark Brown return ret; 616973838a0SMark Brown } 617973838a0SMark Brown } 618973838a0SMark Brown } 619973838a0SMark Brown 6202159ad93SMark Brown ret = wm_adsp2_ena(dsp); 6212159ad93SMark Brown if (ret != 0) 6222159ad93SMark Brown return ret; 6232159ad93SMark Brown 6242159ad93SMark Brown ret = wm_adsp_load(dsp); 6252159ad93SMark Brown if (ret != 0) 6262159ad93SMark Brown goto err; 6272159ad93SMark Brown 6282159ad93SMark Brown ret = wm_adsp_load_coeff(dsp); 6292159ad93SMark Brown if (ret != 0) 6302159ad93SMark Brown goto err; 6312159ad93SMark Brown 6322159ad93SMark Brown ret = regmap_update_bits(dsp->regmap, 6332159ad93SMark Brown dsp->base + ADSP2_CONTROL, 634a7f9be7eSMark Brown ADSP2_CORE_ENA | ADSP2_START, 635a7f9be7eSMark Brown ADSP2_CORE_ENA | ADSP2_START); 6362159ad93SMark Brown if (ret != 0) 6372159ad93SMark Brown goto err; 6382159ad93SMark Brown break; 6392159ad93SMark Brown 6402159ad93SMark Brown case SND_SOC_DAPM_PRE_PMD: 6412159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, 642a7f9be7eSMark Brown ADSP2_SYS_ENA | ADSP2_CORE_ENA | 643a7f9be7eSMark Brown ADSP2_START, 0); 644973838a0SMark Brown 645973838a0SMark Brown if (dsp->dvfs) { 646973838a0SMark Brown ret = regulator_set_voltage(dsp->dvfs, 1200000, 647973838a0SMark Brown 1800000); 648973838a0SMark Brown if (ret != 0) 649973838a0SMark Brown dev_warn(dsp->dev, 650973838a0SMark Brown "Failed to lower supply: %d\n", 651973838a0SMark Brown ret); 652973838a0SMark Brown 653973838a0SMark Brown ret = regulator_disable(dsp->dvfs); 654973838a0SMark Brown if (ret != 0) 655973838a0SMark Brown dev_err(dsp->dev, 656973838a0SMark Brown "Failed to enable supply: %d\n", 657973838a0SMark Brown ret); 658973838a0SMark Brown } 6592159ad93SMark Brown break; 6602159ad93SMark Brown 6612159ad93SMark Brown default: 6622159ad93SMark Brown break; 6632159ad93SMark Brown } 6642159ad93SMark Brown 6652159ad93SMark Brown return 0; 6662159ad93SMark Brown err: 6672159ad93SMark Brown regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, 668a7f9be7eSMark Brown ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); 6692159ad93SMark Brown return ret; 6702159ad93SMark Brown } 6712159ad93SMark Brown EXPORT_SYMBOL_GPL(wm_adsp2_event); 672973838a0SMark Brown 673973838a0SMark Brown int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs) 674973838a0SMark Brown { 675973838a0SMark Brown int ret; 676973838a0SMark Brown 67710a2b662SMark Brown /* 67810a2b662SMark Brown * Disable the DSP memory by default when in reset for a small 67910a2b662SMark Brown * power saving. 68010a2b662SMark Brown */ 68110a2b662SMark Brown ret = regmap_update_bits(adsp->regmap, adsp->base + ADSP2_CONTROL, 68210a2b662SMark Brown ADSP2_MEM_ENA, 0); 68310a2b662SMark Brown if (ret != 0) { 68410a2b662SMark Brown adsp_err(adsp, "Failed to clear memory retention: %d\n", ret); 68510a2b662SMark Brown return ret; 68610a2b662SMark Brown } 68710a2b662SMark Brown 688973838a0SMark Brown if (dvfs) { 689973838a0SMark Brown adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD"); 690973838a0SMark Brown if (IS_ERR(adsp->dvfs)) { 691973838a0SMark Brown ret = PTR_ERR(adsp->dvfs); 692973838a0SMark Brown dev_err(adsp->dev, "Failed to get DCVDD: %d\n", ret); 693973838a0SMark Brown return ret; 694973838a0SMark Brown } 695973838a0SMark Brown 696973838a0SMark Brown ret = regulator_enable(adsp->dvfs); 697973838a0SMark Brown if (ret != 0) { 698973838a0SMark Brown dev_err(adsp->dev, "Failed to enable DCVDD: %d\n", 699973838a0SMark Brown ret); 700973838a0SMark Brown return ret; 701973838a0SMark Brown } 702973838a0SMark Brown 703973838a0SMark Brown ret = regulator_set_voltage(adsp->dvfs, 1200000, 1800000); 704973838a0SMark Brown if (ret != 0) { 705973838a0SMark Brown dev_err(adsp->dev, "Failed to initialise DVFS: %d\n", 706973838a0SMark Brown ret); 707973838a0SMark Brown return ret; 708973838a0SMark Brown } 709973838a0SMark Brown 710973838a0SMark Brown ret = regulator_disable(adsp->dvfs); 711973838a0SMark Brown if (ret != 0) { 712973838a0SMark Brown dev_err(adsp->dev, "Failed to disable DCVDD: %d\n", 713973838a0SMark Brown ret); 714973838a0SMark Brown return ret; 715973838a0SMark Brown } 716973838a0SMark Brown } 717973838a0SMark Brown 718973838a0SMark Brown return 0; 719973838a0SMark Brown } 720973838a0SMark Brown EXPORT_SYMBOL_GPL(wm_adsp2_init); 721