From: Mark Brown Date: Wed, 12 Mar 2014 23:04:35 +0000 (+0000) Subject: Merge remote-tracking branches 'asoc/topic/ml26124', 'asoc/topic/of', 'asoc/topic... X-Git-Tag: v3.15-rc1~36^2~1^2~5^2~5 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=a4b12990;p=karo-tx-linux.git Merge remote-tracking branches 'asoc/topic/ml26124', 'asoc/topic/of', 'asoc/topic/omap', 'asoc/topic/pxa' and 'asoc/topic/rcar' into asoc-next --- a4b12990b68079290ab62799035afe175b4bdc23 diff --cc include/trace/events/hswadsp.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..0f78bbb02002 new file mode 100644 --- /dev/null +++ b/include/trace/events/hswadsp.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,384 @@@@@@@ ++++++#undef TRACE_SYSTEM ++++++#define TRACE_SYSTEM hswadsp ++++++ ++++++#if !defined(_TRACE_HSWADSP_H) || defined(TRACE_HEADER_MULTI_READ) ++++++#define _TRACE_HSWADSP_H ++++++ ++++++#include ++++++#include ++++++#include ++++++ ++++++struct sst_hsw; ++++++struct sst_hsw_stream; ++++++struct sst_hsw_ipc_stream_free_req; ++++++struct sst_hsw_ipc_volume_req; ++++++struct sst_hsw_ipc_stream_alloc_req; ++++++struct sst_hsw_audio_data_format_ipc; ++++++struct sst_hsw_ipc_stream_info_reply; ++++++struct sst_hsw_ipc_device_config_req; ++++++ ++++++DECLARE_EVENT_CLASS(sst_irq, ++++++ ++++++ TP_PROTO(uint32_t status, uint32_t mask), ++++++ ++++++ TP_ARGS(status, mask), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( unsigned int, status ) ++++++ __field( unsigned int, mask ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->status = status; ++++++ __entry->mask = mask; ++++++ ), ++++++ ++++++ TP_printk("status 0x%8.8x mask 0x%8.8x", ++++++ (unsigned int)__entry->status, (unsigned int)__entry->mask) ++++++); ++++++ ++++++DEFINE_EVENT(sst_irq, sst_irq_busy, ++++++ ++++++ TP_PROTO(unsigned int status, unsigned int mask), ++++++ ++++++ TP_ARGS(status, mask) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_irq, sst_irq_done, ++++++ ++++++ TP_PROTO(unsigned int status, unsigned int mask), ++++++ ++++++ TP_ARGS(status, mask) ++++++ ++++++); ++++++ ++++++DECLARE_EVENT_CLASS(ipc, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val), ++++++ ++++++ TP_STRUCT__entry( ++++++ __string( name, name ) ++++++ __field( unsigned int, val ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __assign_str(name, name); ++++++ __entry->val = val; ++++++ ), ++++++ ++++++ TP_printk("%s 0x%8.8x", __get_str(name), (unsigned int)__entry->val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(ipc, ipc_request, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(ipc, ipc_reply, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(ipc, ipc_pending_reply, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(ipc, ipc_notification, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(ipc, ipc_error, ++++++ ++++++ TP_PROTO(const char *name, int val), ++++++ ++++++ TP_ARGS(name, val) ++++++ ++++++); ++++++ ++++++DECLARE_EVENT_CLASS(stream_position, ++++++ ++++++ TP_PROTO(unsigned int id, unsigned int pos), ++++++ ++++++ TP_ARGS(id, pos), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( unsigned int, id ) ++++++ __field( unsigned int, pos ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = id; ++++++ __entry->pos = pos; ++++++ ), ++++++ ++++++ TP_printk("id %d position 0x%x", ++++++ (unsigned int)__entry->id, (unsigned int)__entry->pos) ++++++); ++++++ ++++++DEFINE_EVENT(stream_position, stream_read_position, ++++++ ++++++ TP_PROTO(unsigned int id, unsigned int pos), ++++++ ++++++ TP_ARGS(id, pos) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(stream_position, stream_write_position, ++++++ ++++++ TP_PROTO(unsigned int id, unsigned int pos), ++++++ ++++++ TP_ARGS(id, pos) ++++++ ++++++); ++++++ ++++++TRACE_EVENT(hsw_stream_buffer, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream), ++++++ ++++++ TP_ARGS(stream), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( int, id ) ++++++ __field( int, pt_addr ) ++++++ __field( int, num_pages ) ++++++ __field( int, ring_size ) ++++++ __field( int, ring_offset ) ++++++ __field( int, first_pfn ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->pt_addr = stream->request.ringinfo.ring_pt_address; ++++++ __entry->num_pages = stream->request.ringinfo.num_pages; ++++++ __entry->ring_size = stream->request.ringinfo.ring_size; ++++++ __entry->ring_offset = stream->request.ringinfo.ring_offset; ++++++ __entry->first_pfn = stream->request.ringinfo.ring_first_pfn; ++++++ ), ++++++ ++++++ TP_printk("stream %d ring addr 0x%x pages %d size 0x%x offset 0x%x PFN 0x%x", ++++++ (int) __entry->id, (int)__entry->pt_addr, ++++++ (int)__entry->num_pages, (int)__entry->ring_size, ++++++ (int)__entry->ring_offset, (int)__entry->first_pfn) ++++++); ++++++ ++++++TRACE_EVENT(hsw_stream_alloc_reply, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream), ++++++ ++++++ TP_ARGS(stream), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( int, id ) ++++++ __field( int, stream_id ) ++++++ __field( int, mixer_id ) ++++++ __field( int, peak0 ) ++++++ __field( int, peak1 ) ++++++ __field( int, vol0 ) ++++++ __field( int, vol1 ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->stream_id = stream->reply.stream_hw_id; ++++++ __entry->mixer_id = stream->reply.mixer_hw_id; ++++++ __entry->peak0 = stream->reply.peak_meter_register_address[0]; ++++++ __entry->peak1 = stream->reply.peak_meter_register_address[1]; ++++++ __entry->vol0 = stream->reply.volume_register_address[0]; ++++++ __entry->vol1 = stream->reply.volume_register_address[1]; ++++++ ), ++++++ ++++++ TP_printk("stream %d hw id %d mixer %d peak 0x%x:0x%x vol 0x%x,0x%x", ++++++ (int) __entry->id, (int) __entry->stream_id, (int)__entry->mixer_id, ++++++ (int)__entry->peak0, (int)__entry->peak1, ++++++ (int)__entry->vol0, (int)__entry->vol1) ++++++); ++++++ ++++++TRACE_EVENT(hsw_mixer_info_reply, ++++++ ++++++ TP_PROTO(struct sst_hsw_ipc_stream_info_reply *reply), ++++++ ++++++ TP_ARGS(reply), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( int, mixer_id ) ++++++ __field( int, peak0 ) ++++++ __field( int, peak1 ) ++++++ __field( int, vol0 ) ++++++ __field( int, vol1 ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->mixer_id = reply->mixer_hw_id; ++++++ __entry->peak0 = reply->peak_meter_register_address[0]; ++++++ __entry->peak1 = reply->peak_meter_register_address[1]; ++++++ __entry->vol0 = reply->volume_register_address[0]; ++++++ __entry->vol1 = reply->volume_register_address[1]; ++++++ ), ++++++ ++++++ TP_printk("mixer id %d peak 0x%x:0x%x vol 0x%x,0x%x", ++++++ (int)__entry->mixer_id, ++++++ (int)__entry->peak0, (int)__entry->peak1, ++++++ (int)__entry->vol0, (int)__entry->vol1) ++++++); ++++++ ++++++TRACE_EVENT(hsw_stream_data_format, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream, ++++++ struct sst_hsw_audio_data_format_ipc *req), ++++++ ++++++ TP_ARGS(stream, req), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( uint32_t, id ) ++++++ __field( uint32_t, frequency ) ++++++ __field( uint32_t, bitdepth ) ++++++ __field( uint32_t, map ) ++++++ __field( uint32_t, config ) ++++++ __field( uint32_t, style ) ++++++ __field( uint8_t, ch_num ) ++++++ __field( uint8_t, valid_bit ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->frequency = req->frequency; ++++++ __entry->bitdepth = req->bitdepth; ++++++ __entry->map = req->map; ++++++ __entry->config = req->config; ++++++ __entry->style = req->style; ++++++ __entry->ch_num = req->ch_num; ++++++ __entry->valid_bit = req->valid_bit; ++++++ ), ++++++ ++++++ TP_printk("stream %d freq %d depth %d map 0x%x config 0x%x style 0x%x ch %d bits %d", ++++++ (int) __entry->id, (uint32_t)__entry->frequency, ++++++ (uint32_t)__entry->bitdepth, (uint32_t)__entry->map, ++++++ (uint32_t)__entry->config, (uint32_t)__entry->style, ++++++ (uint8_t)__entry->ch_num, (uint8_t)__entry->valid_bit) ++++++); ++++++ ++++++TRACE_EVENT(hsw_stream_alloc_request, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream, ++++++ struct sst_hsw_ipc_stream_alloc_req *req), ++++++ ++++++ TP_ARGS(stream, req), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( uint32_t, id ) ++++++ __field( uint8_t, path_id ) ++++++ __field( uint8_t, stream_type ) ++++++ __field( uint8_t, format_id ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->path_id = req->path_id; ++++++ __entry->stream_type = req->stream_type; ++++++ __entry->format_id = req->format_id; ++++++ ), ++++++ ++++++ TP_printk("stream %d path %d type %d format %d", ++++++ (int) __entry->id, (uint8_t)__entry->path_id, ++++++ (uint8_t)__entry->stream_type, (uint8_t)__entry->format_id) ++++++); ++++++ ++++++TRACE_EVENT(hsw_stream_free_req, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream, ++++++ struct sst_hsw_ipc_stream_free_req *req), ++++++ ++++++ TP_ARGS(stream, req), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( int, id ) ++++++ __field( int, stream_id ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->stream_id = req->stream_id; ++++++ ), ++++++ ++++++ TP_printk("stream %d hw id %d", ++++++ (int) __entry->id, (int) __entry->stream_id) ++++++); ++++++ ++++++TRACE_EVENT(hsw_volume_req, ++++++ ++++++ TP_PROTO(struct sst_hsw_stream *stream, ++++++ struct sst_hsw_ipc_volume_req *req), ++++++ ++++++ TP_ARGS(stream, req), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( int, id ) ++++++ __field( uint32_t, channel ) ++++++ __field( uint32_t, target_volume ) ++++++ __field( uint64_t, curve_duration ) ++++++ __field( uint32_t, curve_type ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->id = stream->host_id; ++++++ __entry->channel = req->channel; ++++++ __entry->target_volume = req->target_volume; ++++++ __entry->curve_duration = req->curve_duration; ++++++ __entry->curve_type = req->curve_type; ++++++ ), ++++++ ++++++ TP_printk("stream %d chan 0x%x vol %d duration %llu type %d", ++++++ (int) __entry->id, (uint32_t) __entry->channel, ++++++ (uint32_t)__entry->target_volume, ++++++ (uint64_t)__entry->curve_duration, ++++++ (uint32_t)__entry->curve_type) ++++++); ++++++ ++++++TRACE_EVENT(hsw_device_config_req, ++++++ ++++++ TP_PROTO(struct sst_hsw_ipc_device_config_req *req), ++++++ ++++++ TP_ARGS(req), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( uint32_t, ssp ) ++++++ __field( uint32_t, clock_freq ) ++++++ __field( uint32_t, mode ) ++++++ __field( uint16_t, clock_divider ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->ssp = req->ssp_interface; ++++++ __entry->clock_freq = req->clock_frequency; ++++++ __entry->mode = req->mode; ++++++ __entry->clock_divider = req->clock_divider; ++++++ ), ++++++ ++++++ TP_printk("SSP %d Freq %d mode %d div %d", ++++++ (uint32_t)__entry->ssp, ++++++ (uint32_t)__entry->clock_freq, (uint32_t)__entry->mode, ++++++ (uint32_t)__entry->clock_divider) ++++++); ++++++ ++++++#endif /* _TRACE_HSWADSP_H */ ++++++ ++++++/* This part must be outside protection */ ++++++#include diff --cc include/trace/events/intel-sst.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..76c72d3f1902 new file mode 100644 --- /dev/null +++ b/include/trace/events/intel-sst.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,148 @@@@@@@ ++++++#undef TRACE_SYSTEM ++++++#define TRACE_SYSTEM intel-sst ++++++ ++++++#if !defined(_TRACE_INTEL_SST_H) || defined(TRACE_HEADER_MULTI_READ) ++++++#define _TRACE_INTEL_SST_H ++++++ ++++++#include ++++++#include ++++++#include ++++++ ++++++DECLARE_EVENT_CLASS(sst_ipc_msg, ++++++ ++++++ TP_PROTO(unsigned int val), ++++++ ++++++ TP_ARGS(val), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( unsigned int, val ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->val = val; ++++++ ), ++++++ ++++++ TP_printk("0x%8.8x", (unsigned int)__entry->val) ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_msg, sst_ipc_msg_tx, ++++++ ++++++ TP_PROTO(unsigned int val), ++++++ ++++++ TP_ARGS(val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_msg, sst_ipc_msg_rx, ++++++ ++++++ TP_PROTO(unsigned int val), ++++++ ++++++ TP_ARGS(val) ++++++ ++++++); ++++++ ++++++DECLARE_EVENT_CLASS(sst_ipc_mailbox, ++++++ ++++++ TP_PROTO(unsigned int offset, unsigned int val), ++++++ ++++++ TP_ARGS(offset, val), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( unsigned int, offset ) ++++++ __field( unsigned int, val ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->offset = offset; ++++++ __entry->val = val; ++++++ ), ++++++ ++++++ TP_printk(" 0x%4.4x = 0x%8.8x", ++++++ (unsigned int)__entry->offset, (unsigned int)__entry->val) ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox, sst_ipc_inbox_rdata, ++++++ ++++++ TP_PROTO(unsigned int offset, unsigned int val), ++++++ ++++++ TP_ARGS(offset, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox, sst_ipc_inbox_wdata, ++++++ ++++++ TP_PROTO(unsigned int offset, unsigned int val), ++++++ ++++++ TP_ARGS(offset, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox, sst_ipc_outbox_rdata, ++++++ ++++++ TP_PROTO(unsigned int offset, unsigned int val), ++++++ ++++++ TP_ARGS(offset, val) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox, sst_ipc_outbox_wdata, ++++++ ++++++ TP_PROTO(unsigned int offset, unsigned int val), ++++++ ++++++ TP_ARGS(offset, val) ++++++ ++++++); ++++++ ++++++DECLARE_EVENT_CLASS(sst_ipc_mailbox_info, ++++++ ++++++ TP_PROTO(unsigned int size), ++++++ ++++++ TP_ARGS(size), ++++++ ++++++ TP_STRUCT__entry( ++++++ __field( unsigned int, size ) ++++++ ), ++++++ ++++++ TP_fast_assign( ++++++ __entry->size = size; ++++++ ), ++++++ ++++++ TP_printk("Mailbox bytes 0x%8.8x", (unsigned int)__entry->size) ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox_info, sst_ipc_inbox_read, ++++++ ++++++ TP_PROTO(unsigned int size), ++++++ ++++++ TP_ARGS(size) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox_info, sst_ipc_inbox_write, ++++++ ++++++ TP_PROTO(unsigned int size), ++++++ ++++++ TP_ARGS(size) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox_info, sst_ipc_outbox_read, ++++++ ++++++ TP_PROTO(unsigned int size), ++++++ ++++++ TP_ARGS(size) ++++++ ++++++); ++++++ ++++++DEFINE_EVENT(sst_ipc_mailbox_info, sst_ipc_outbox_write, ++++++ ++++++ TP_PROTO(unsigned int size), ++++++ ++++++ TP_ARGS(size) ++++++ ++++++); ++++++ ++++++#endif /* _TRACE_SST_H */ ++++++ ++++++/* This part must be outside protection */ ++++++#include diff --cc sound/soc/intel/Kconfig index 61c10bf503d2,61c10bf503d2,61c10bf503d2,61c10bf503d2,61c10bf503d2,61c10bf503d2..4577b69fcf2c --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@@@@@@ -2,12 -2,12 -2,12 -2,12 -2,12 -2,12 +2,50 @@@@@@@ config SND_MFLD_MACHIN tristate "SOC Machine Audio driver for Intel Medfield MID platform" depends on INTEL_SCU_IPC select SND_SOC_SN95031 ------ select SND_SST_PLATFORM ++++++ select SND_SST_MFLD_PLATFORM help This adds support for ASoC machine driver for Intel(R) MID Medfield platform used as alsa device in audio substem in Intel(R) MID devices Say Y if you have such a device If unsure select "N". ------config SND_SST_PLATFORM ++++++config SND_SST_MFLD_PLATFORM tristate ++++++ ++++++config SND_SOC_INTEL_SST ++++++ tristate "ASoC support for Intel(R) Smart Sound Technology" ++++++ select SND_SOC_INTEL_SST_ACPI if ACPI ++++++ depends on (X86 || COMPILE_TEST) ++++++ help ++++++ This adds support for Intel(R) Smart Sound Technology (SST). ++++++ Say Y if you have such a device ++++++ If unsure select "N". ++++++ ++++++config SND_SOC_INTEL_SST_ACPI ++++++ tristate ++++++ ++++++config SND_SOC_INTEL_HASWELL ++++++ tristate ++++++ ++++++config SND_SOC_INTEL_BAYTRAIL ++++++ tristate ++++++ ++++++config SND_SOC_INTEL_HASWELL_MACH ++++++ tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" ++++++ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS ++++++ select SND_SOC_INTEL_HASWELL ++++++ select SND_SOC_RT5640 ++++++ help ++++++ This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell ++++++ Ultrabook platforms. ++++++ Say Y if you have such a device ++++++ If unsure select "N". ++++++ ++++++config SND_SOC_INTEL_BYT_RT5640_MACH ++++++ tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec" ++++++ depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS ++++++ select SND_SOC_INTEL_BAYTRAIL ++++++ select SND_SOC_RT5640 ++++++ help ++++++ This adds audio driver for Intel Baytrail platform based boards ++++++ with the RT5640 audio codec. diff --cc sound/soc/intel/Makefile index 639883339465,639883339465,639883339465,639883339465,639883339465,639883339465..edeb79ae3dff --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@@@@@@ -1,5 -1,5 -1,5 -1,5 -1,5 -1,5 +1,28 @@@@@@@ ------snd-soc-sst-platform-objs := sst_platform.o ++++++# Core support ++++++snd-soc-sst-dsp-objs := sst-dsp.o sst-firmware.o ++++++snd-soc-sst-acpi-objs := sst-acpi.o ++++++ ++++++snd-soc-sst-mfld-platform-objs := sst-mfld-platform.o snd-soc-mfld-machine-objs := mfld_machine.o ------obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o ++++++obj-$(CONFIG_SND_SST_MFLD_PLATFORM) += snd-soc-sst-mfld-platform.o obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o ++++++ ++++++obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o ++++++obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o ++++++ ++++++# Platform Support ++++++snd-soc-sst-haswell-pcm-objs := \ ++++++ sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o ++++++snd-soc-sst-baytrail-pcm-objs := \ ++++++ sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o ++++++ ++++++obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o ++++++obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o ++++++ ++++++# Machine support ++++++snd-soc-sst-haswell-objs := haswell.o ++++++snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o ++++++ ++++++obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o ++++++obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o diff --cc sound/soc/intel/byt-rt5640.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..eff97c8e5218 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/byt-rt5640.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,187 @@@@@@@ ++++++/* ++++++ * Intel Baytrail SST RT5640 machine driver ++++++ * Copyright (c) 2014, Intel Corporation. ++++++ * ++++++ * This program is free software; you can redistribute it and/or modify it ++++++ * under the terms and conditions of the GNU General Public License, ++++++ * version 2, as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope it will be useful, but WITHOUT ++++++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++++++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++++++ * more details. ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include "../codecs/rt5640.h" ++++++ ++++++#include "sst-dsp.h" ++++++ ++++++static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { ++++++ SND_SOC_DAPM_HP("Headphone", NULL), ++++++ SND_SOC_DAPM_MIC("Headset Mic", NULL), ++++++ SND_SOC_DAPM_MIC("Internal Mic", NULL), ++++++ SND_SOC_DAPM_SPK("Speaker", NULL), ++++++}; ++++++ ++++++static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { ++++++ {"IN2P", NULL, "Headset Mic"}, ++++++ {"IN2N", NULL, "Headset Mic"}, ++++++ {"DMIC1", NULL, "Internal Mic"}, ++++++ {"Headphone", NULL, "HPOL"}, ++++++ {"Headphone", NULL, "HPOR"}, ++++++ {"Speaker", NULL, "SPOLP"}, ++++++ {"Speaker", NULL, "SPOLN"}, ++++++ {"Speaker", NULL, "SPORP"}, ++++++ {"Speaker", NULL, "SPORN"}, ++++++}; ++++++ ++++++static const struct snd_kcontrol_new byt_rt5640_controls[] = { ++++++ SOC_DAPM_PIN_SWITCH("Headphone"), ++++++ SOC_DAPM_PIN_SWITCH("Headset Mic"), ++++++ SOC_DAPM_PIN_SWITCH("Internal Mic"), ++++++ SOC_DAPM_PIN_SWITCH("Speaker"), ++++++}; ++++++ ++++++static int byt_rt5640_hw_params(struct snd_pcm_substream *substream, ++++++ struct snd_pcm_hw_params *params) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++++++ int ret; ++++++ ++++++ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, ++++++ params_rate(params) * 256, ++++++ SND_SOC_CLOCK_IN); ++++++ if (ret < 0) { ++++++ dev_err(codec_dai->dev, "can't set codec clock %d\n", ret); ++++++ return ret; ++++++ } ++++++ ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, ++++++ params_rate(params) * 64, ++++++ params_rate(params) * 256); ++++++ if (ret < 0) { ++++++ dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); ++++++ return ret; ++++++ } ++++++ return 0; ++++++} ++++++ ++++++static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) ++++++{ ++++++ int ret; ++++++ struct snd_soc_codec *codec = runtime->codec; ++++++ struct snd_soc_dapm_context *dapm = &codec->dapm; ++++++ struct snd_soc_card *card = runtime->card; ++++++ ++++++ card->dapm.idle_bias_off = true; ++++++ ++++++ ret = snd_soc_add_card_controls(card, byt_rt5640_controls, ++++++ ARRAY_SIZE(byt_rt5640_controls)); ++++++ if (ret) { ++++++ dev_err(card->dev, "unable to add card controls\n"); ++++++ return ret; ++++++ } ++++++ ++++++ snd_soc_dapm_ignore_suspend(dapm, "HPOL"); ++++++ snd_soc_dapm_ignore_suspend(dapm, "HPOR"); ++++++ ++++++ snd_soc_dapm_ignore_suspend(dapm, "SPOLP"); ++++++ snd_soc_dapm_ignore_suspend(dapm, "SPOLN"); ++++++ snd_soc_dapm_ignore_suspend(dapm, "SPORP"); ++++++ snd_soc_dapm_ignore_suspend(dapm, "SPORN"); ++++++ ++++++ snd_soc_dapm_enable_pin(dapm, "Headset Mic"); ++++++ snd_soc_dapm_enable_pin(dapm, "Headphone"); ++++++ snd_soc_dapm_enable_pin(dapm, "Speaker"); ++++++ snd_soc_dapm_enable_pin(dapm, "Internal Mic"); ++++++ ++++++ snd_soc_dapm_sync(dapm); ++++++ return ret; ++++++} ++++++ ++++++static struct snd_soc_ops byt_rt5640_ops = { ++++++ .hw_params = byt_rt5640_hw_params, ++++++}; ++++++ ++++++static struct snd_soc_dai_link byt_rt5640_dais[] = { ++++++ { ++++++ .name = "Baytrail Audio", ++++++ .stream_name = "Audio", ++++++ .cpu_dai_name = "Front-cpu-dai", ++++++ .codec_dai_name = "rt5640-aif1", ++++++ .codec_name = "i2c-10EC5640:00", ++++++ .platform_name = "baytrail-pcm-audio", ++++++ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | ++++++ SND_SOC_DAIFMT_CBS_CFS, ++++++ .init = byt_rt5640_init, ++++++ .ignore_suspend = 1, ++++++ .ops = &byt_rt5640_ops, ++++++ }, ++++++ { ++++++ .name = "Baytrail Voice", ++++++ .stream_name = "Voice", ++++++ .cpu_dai_name = "Mic1-cpu-dai", ++++++ .codec_dai_name = "rt5640-aif1", ++++++ .codec_name = "i2c-10EC5640:00", ++++++ .platform_name = "baytrail-pcm-audio", ++++++ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | ++++++ SND_SOC_DAIFMT_CBS_CFS, ++++++ .init = NULL, ++++++ .ignore_suspend = 1, ++++++ .ops = &byt_rt5640_ops, ++++++ }, ++++++}; ++++++ ++++++static struct snd_soc_card byt_rt5640_card = { ++++++ .name = "byt-rt5640", ++++++ .dai_link = byt_rt5640_dais, ++++++ .num_links = ARRAY_SIZE(byt_rt5640_dais), ++++++ .dapm_widgets = byt_rt5640_widgets, ++++++ .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), ++++++ .dapm_routes = byt_rt5640_audio_map, ++++++ .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), ++++++}; ++++++ ++++++static int byt_rt5640_probe(struct platform_device *pdev) ++++++{ ++++++ struct snd_soc_card *card = &byt_rt5640_card; ++++++ struct device *dev = &pdev->dev; ++++++ ++++++ card->dev = &pdev->dev; ++++++ dev_set_drvdata(dev, card); ++++++ return snd_soc_register_card(card); ++++++} ++++++ ++++++static int byt_rt5640_remove(struct platform_device *pdev) ++++++{ ++++++ struct snd_soc_card *card = platform_get_drvdata(pdev); ++++++ ++++++ snd_soc_unregister_card(card); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct platform_driver byt_rt5640_audio = { ++++++ .probe = byt_rt5640_probe, ++++++ .remove = byt_rt5640_remove, ++++++ .driver = { ++++++ .name = "byt-rt5640", ++++++ .owner = THIS_MODULE, ++++++ }, ++++++}; ++++++module_platform_driver(byt_rt5640_audio) ++++++ ++++++MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); ++++++MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); ++++++MODULE_LICENSE("GPL v2"); ++++++MODULE_ALIAS("platform:byt-rt5640"); diff --cc sound/soc/intel/haswell.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..54345a2a7386 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/haswell.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,235 @@@@@@@ ++++++/* ++++++ * Intel Haswell Lynxpoint SST Audio ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++#include "sst-haswell-ipc.h" ++++++ ++++++#include "../codecs/rt5640.h" ++++++ ++++++/* Haswell ULT platforms have a Headphone and Mic jack */ ++++++static const struct snd_soc_dapm_widget haswell_widgets[] = { ++++++ SND_SOC_DAPM_HP("Headphones", NULL), ++++++ SND_SOC_DAPM_MIC("Mic", NULL), ++++++}; ++++++ ++++++static const struct snd_soc_dapm_route haswell_rt5640_map[] = { ++++++ ++++++ {"Headphones", NULL, "HPOR"}, ++++++ {"Headphones", NULL, "HPOL"}, ++++++ {"IN2P", NULL, "Mic"}, ++++++ ++++++ /* CODEC BE connections */ ++++++ {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, ++++++ {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, ++++++}; ++++++ ++++++static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, ++++++ struct snd_pcm_hw_params *params) ++++++{ ++++++ struct snd_interval *rate = hw_param_interval(params, ++++++ SNDRV_PCM_HW_PARAM_RATE); ++++++ struct snd_interval *channels = hw_param_interval(params, ++++++ SNDRV_PCM_HW_PARAM_CHANNELS); ++++++ ++++++ /* The ADSP will covert the FE rate to 48k, stereo */ ++++++ rate->min = rate->max = 48000; ++++++ channels->min = channels->max = 2; ++++++ ++++++ /* set SSP0 to 16 bit */ ++++++ snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - ++++++ SNDRV_PCM_HW_PARAM_FIRST_MASK], ++++++ SNDRV_PCM_FORMAT_S16_LE); ++++++ return 0; ++++++} ++++++ ++++++static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream, ++++++ struct snd_pcm_hw_params *params) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++++++ int ret; ++++++ ++++++ ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, ++++++ SND_SOC_CLOCK_IN); ++++++ ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "can't set codec sysclk configuration\n"); ++++++ return ret; ++++++ } ++++++ ++++++ /* set correct codec filter for DAI format and clock config */ ++++++ snd_soc_update_bits(rtd->codec, 0x83, 0xffff, 0x8000); ++++++ ++++++ return ret; ++++++} ++++++ ++++++static struct snd_soc_ops haswell_rt5640_ops = { ++++++ .hw_params = haswell_rt5640_hw_params, ++++++}; ++++++ ++++++static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd) ++++++{ ++++++ struct snd_soc_codec *codec = rtd->codec; ++++++ struct snd_soc_dapm_context *dapm = &codec->dapm; ++++++ struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); ++++++ struct sst_hsw *haswell = pdata->dsp; ++++++ int ret; ++++++ ++++++ /* Set ADSP SSP port settings */ ++++++ ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0, ++++++ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, ++++++ SST_HSW_DEVICE_CLOCK_MASTER, 9); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "failed to set device config\n"); ++++++ return ret; ++++++ } ++++++ ++++++ /* always connected */ ++++++ snd_soc_dapm_enable_pin(dapm, "Headphones"); ++++++ snd_soc_dapm_enable_pin(dapm, "Mic"); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct snd_soc_dai_link haswell_rt5640_dais[] = { ++++++ /* Front End DAI links */ ++++++ { ++++++ .name = "System", ++++++ .stream_name = "System Playback", ++++++ .cpu_dai_name = "System Pin", ++++++ .platform_name = "haswell-pcm-audio", ++++++ .dynamic = 1, ++++++ .codec_name = "snd-soc-dummy", ++++++ .codec_dai_name = "snd-soc-dummy-dai", ++++++ .init = haswell_rtd_init, ++++++ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, ++++++ .dpcm_playback = 1, ++++++ }, ++++++ { ++++++ .name = "Offload0", ++++++ .stream_name = "Offload0 Playback", ++++++ .cpu_dai_name = "Offload0 Pin", ++++++ .platform_name = "haswell-pcm-audio", ++++++ .dynamic = 1, ++++++ .codec_name = "snd-soc-dummy", ++++++ .codec_dai_name = "snd-soc-dummy-dai", ++++++ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, ++++++ .dpcm_playback = 1, ++++++ }, ++++++ { ++++++ .name = "Offload1", ++++++ .stream_name = "Offload1 Playback", ++++++ .cpu_dai_name = "Offload1 Pin", ++++++ .platform_name = "haswell-pcm-audio", ++++++ .dynamic = 1, ++++++ .codec_name = "snd-soc-dummy", ++++++ .codec_dai_name = "snd-soc-dummy-dai", ++++++ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, ++++++ .dpcm_playback = 1, ++++++ }, ++++++ { ++++++ .name = "Loopback", ++++++ .stream_name = "Loopback", ++++++ .cpu_dai_name = "Loopback Pin", ++++++ .platform_name = "haswell-pcm-audio", ++++++ .dynamic = 0, ++++++ .codec_name = "snd-soc-dummy", ++++++ .codec_dai_name = "snd-soc-dummy-dai", ++++++ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, ++++++ .dpcm_capture = 1, ++++++ }, ++++++ { ++++++ .name = "Capture", ++++++ .stream_name = "Capture", ++++++ .cpu_dai_name = "Capture Pin", ++++++ .platform_name = "haswell-pcm-audio", ++++++ .dynamic = 1, ++++++ .codec_name = "snd-soc-dummy", ++++++ .codec_dai_name = "snd-soc-dummy-dai", ++++++ .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, ++++++ .dpcm_capture = 1, ++++++ }, ++++++ ++++++ /* Back End DAI links */ ++++++ { ++++++ /* SSP0 - Codec */ ++++++ .name = "Codec", ++++++ .be_id = 0, ++++++ .cpu_dai_name = "snd-soc-dummy-dai", ++++++ .platform_name = "snd-soc-dummy", ++++++ .no_pcm = 1, ++++++ .codec_name = "i2c-INT33CA:00", ++++++ .codec_dai_name = "rt5640-aif1", ++++++ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | ++++++ SND_SOC_DAIFMT_CBS_CFS, ++++++ .ignore_suspend = 1, ++++++ .ignore_pmdown_time = 1, ++++++ .be_hw_params_fixup = haswell_ssp0_fixup, ++++++ .ops = &haswell_rt5640_ops, ++++++ .dpcm_playback = 1, ++++++ .dpcm_capture = 1, ++++++ }, ++++++}; ++++++ ++++++/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */ ++++++static struct snd_soc_card haswell_rt5640 = { ++++++ .name = "haswell-rt5640", ++++++ .owner = THIS_MODULE, ++++++ .dai_link = haswell_rt5640_dais, ++++++ .num_links = ARRAY_SIZE(haswell_rt5640_dais), ++++++ .dapm_widgets = haswell_widgets, ++++++ .num_dapm_widgets = ARRAY_SIZE(haswell_widgets), ++++++ .dapm_routes = haswell_rt5640_map, ++++++ .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map), ++++++ .fully_routed = true, ++++++}; ++++++ ++++++static int haswell_audio_probe(struct platform_device *pdev) ++++++{ ++++++ haswell_rt5640.dev = &pdev->dev; ++++++ ++++++ return snd_soc_register_card(&haswell_rt5640); ++++++} ++++++ ++++++static int haswell_audio_remove(struct platform_device *pdev) ++++++{ ++++++ snd_soc_unregister_card(&haswell_rt5640); ++++++ return 0; ++++++} ++++++ ++++++static struct platform_driver haswell_audio = { ++++++ .probe = haswell_audio_probe, ++++++ .remove = haswell_audio_remove, ++++++ .driver = { ++++++ .name = "haswell-audio", ++++++ .owner = THIS_MODULE, ++++++ }, ++++++}; ++++++ ++++++module_platform_driver(haswell_audio) ++++++ ++++++/* Module information */ ++++++MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); ++++++MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint"); ++++++MODULE_LICENSE("GPL v2"); ++++++MODULE_ALIAS("platform:haswell-audio"); diff --cc sound/soc/intel/sst-acpi.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..5d06eecb6198 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-acpi.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,284 @@@@@@@ ++++++/* ++++++ * Intel SST loader on ACPI systems ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++ ++++++#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000 ++++++#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000 ++++++#define SST_LPT_DSP_DMA_SIZE (1024 - 1) ++++++ ++++++/* Descriptor for SST ASoC machine driver */ ++++++struct sst_acpi_mach { ++++++ /* ACPI ID for the matching machine driver. Audio codec for instance */ ++++++ const u8 id[ACPI_ID_LEN]; ++++++ /* machine driver name */ ++++++ const char *drv_name; ++++++ /* firmware file name */ ++++++ const char *fw_filename; ++++++}; ++++++ ++++++/* Descriptor for setting up SST platform data */ ++++++struct sst_acpi_desc { ++++++ const char *drv_name; ++++++ struct sst_acpi_mach *machines; ++++++ /* Platform resource indexes. Must set to -1 if not used */ ++++++ int resindex_lpe_base; ++++++ int resindex_pcicfg_base; ++++++ int resindex_fw_base; ++++++ int irqindex_host_ipc; ++++++ int resindex_dma_base; ++++++ /* Unique number identifying the SST core on platform */ ++++++ int sst_id; ++++++ /* DMA only valid when resindex_dma_base != -1*/ ++++++ int dma_engine; ++++++ int dma_size; ++++++}; ++++++ ++++++struct sst_acpi_priv { ++++++ struct platform_device *pdev_mach; ++++++ struct platform_device *pdev_pcm; ++++++ struct sst_pdata sst_pdata; ++++++ struct sst_acpi_desc *desc; ++++++ struct sst_acpi_mach *mach; ++++++}; ++++++ ++++++static void sst_acpi_fw_cb(const struct firmware *fw, void *context) ++++++{ ++++++ struct platform_device *pdev = context; ++++++ struct device *dev = &pdev->dev; ++++++ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); ++++++ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; ++++++ struct sst_acpi_desc *desc = sst_acpi->desc; ++++++ struct sst_acpi_mach *mach = sst_acpi->mach; ++++++ ++++++ sst_pdata->fw = fw; ++++++ if (!fw) { ++++++ dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename); ++++++ return; ++++++ } ++++++ ++++++ /* register PCM and DAI driver */ ++++++ sst_acpi->pdev_pcm = ++++++ platform_device_register_data(dev, desc->drv_name, -1, ++++++ sst_pdata, sizeof(*sst_pdata)); ++++++ if (IS_ERR(sst_acpi->pdev_pcm)) { ++++++ dev_err(dev, "Cannot register device %s. Error %d\n", ++++++ desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm)); ++++++ } ++++++ ++++++ return; ++++++} ++++++ ++++++static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, ++++++ void *context, void **ret) ++++++{ ++++++ *(bool *)context = true; ++++++ return AE_OK; ++++++} ++++++ ++++++static struct sst_acpi_mach *sst_acpi_find_machine( ++++++ struct sst_acpi_mach *machines) ++++++{ ++++++ struct sst_acpi_mach *mach; ++++++ bool found = false; ++++++ ++++++ for (mach = machines; mach->id[0]; mach++) ++++++ if (ACPI_SUCCESS(acpi_get_devices(mach->id, ++++++ sst_acpi_mach_match, ++++++ &found, NULL)) && found) ++++++ return mach; ++++++ ++++++ return NULL; ++++++} ++++++ ++++++static int sst_acpi_probe(struct platform_device *pdev) ++++++{ ++++++ const struct acpi_device_id *id; ++++++ struct device *dev = &pdev->dev; ++++++ struct sst_acpi_priv *sst_acpi; ++++++ struct sst_pdata *sst_pdata; ++++++ struct sst_acpi_mach *mach; ++++++ struct sst_acpi_desc *desc; ++++++ struct resource *mmio; ++++++ int ret = 0; ++++++ ++++++ sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL); ++++++ if (sst_acpi == NULL) ++++++ return -ENOMEM; ++++++ ++++++ id = acpi_match_device(dev->driver->acpi_match_table, dev); ++++++ if (!id) ++++++ return -ENODEV; ++++++ ++++++ desc = (struct sst_acpi_desc *)id->driver_data; ++++++ mach = sst_acpi_find_machine(desc->machines); ++++++ if (mach == NULL) { ++++++ dev_err(dev, "No matching ASoC machine driver found\n"); ++++++ return -ENODEV; ++++++ } ++++++ ++++++ sst_pdata = &sst_acpi->sst_pdata; ++++++ sst_pdata->id = desc->sst_id; ++++++ sst_acpi->desc = desc; ++++++ sst_acpi->mach = mach; ++++++ ++++++ if (desc->resindex_dma_base >= 0) { ++++++ sst_pdata->dma_engine = desc->dma_engine; ++++++ sst_pdata->dma_base = desc->resindex_dma_base; ++++++ sst_pdata->dma_size = desc->dma_size; ++++++ } ++++++ ++++++ if (desc->irqindex_host_ipc >= 0) ++++++ sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc); ++++++ ++++++ if (desc->resindex_lpe_base >= 0) { ++++++ mmio = platform_get_resource(pdev, IORESOURCE_MEM, ++++++ desc->resindex_lpe_base); ++++++ if (mmio) { ++++++ sst_pdata->lpe_base = mmio->start; ++++++ sst_pdata->lpe_size = resource_size(mmio); ++++++ } ++++++ } ++++++ ++++++ if (desc->resindex_pcicfg_base >= 0) { ++++++ mmio = platform_get_resource(pdev, IORESOURCE_MEM, ++++++ desc->resindex_pcicfg_base); ++++++ if (mmio) { ++++++ sst_pdata->pcicfg_base = mmio->start; ++++++ sst_pdata->pcicfg_size = resource_size(mmio); ++++++ } ++++++ } ++++++ ++++++ if (desc->resindex_fw_base >= 0) { ++++++ mmio = platform_get_resource(pdev, IORESOURCE_MEM, ++++++ desc->resindex_fw_base); ++++++ if (mmio) { ++++++ sst_pdata->fw_base = mmio->start; ++++++ sst_pdata->fw_size = resource_size(mmio); ++++++ } ++++++ } ++++++ ++++++ platform_set_drvdata(pdev, sst_acpi); ++++++ ++++++ /* register machine driver */ ++++++ sst_acpi->pdev_mach = ++++++ platform_device_register_data(dev, mach->drv_name, -1, ++++++ sst_pdata, sizeof(*sst_pdata)); ++++++ if (IS_ERR(sst_acpi->pdev_mach)) ++++++ return PTR_ERR(sst_acpi->pdev_mach); ++++++ ++++++ /* continue SST probing after firmware is loaded */ ++++++ ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename, ++++++ dev, GFP_KERNEL, pdev, sst_acpi_fw_cb); ++++++ if (ret) ++++++ platform_device_unregister(sst_acpi->pdev_mach); ++++++ ++++++ return ret; ++++++} ++++++ ++++++static int sst_acpi_remove(struct platform_device *pdev) ++++++{ ++++++ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); ++++++ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; ++++++ ++++++ platform_device_unregister(sst_acpi->pdev_mach); ++++++ if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm)) ++++++ platform_device_unregister(sst_acpi->pdev_pcm); ++++++ release_firmware(sst_pdata->fw); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct sst_acpi_mach haswell_machines[] = { ++++++ { "INT33CA", "haswell-audio", "intel/IntcSST1.bin" }, ++++++ {} ++++++}; ++++++ ++++++static struct sst_acpi_desc sst_acpi_haswell_desc = { ++++++ .drv_name = "haswell-pcm-audio", ++++++ .machines = haswell_machines, ++++++ .resindex_lpe_base = 0, ++++++ .resindex_pcicfg_base = 1, ++++++ .resindex_fw_base = -1, ++++++ .irqindex_host_ipc = 0, ++++++ .sst_id = SST_DEV_ID_LYNX_POINT, ++++++ .dma_engine = SST_DMA_TYPE_DW, ++++++ .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET, ++++++ .dma_size = SST_LPT_DSP_DMA_SIZE, ++++++}; ++++++ ++++++static struct sst_acpi_mach broadwell_machines[] = { ++++++ { "INT343A", "broadwell-audio", "intel/IntcSST2.bin" }, ++++++ {} ++++++}; ++++++ ++++++static struct sst_acpi_desc sst_acpi_broadwell_desc = { ++++++ .drv_name = "haswell-pcm-audio", ++++++ .machines = broadwell_machines, ++++++ .resindex_lpe_base = 0, ++++++ .resindex_pcicfg_base = 1, ++++++ .resindex_fw_base = -1, ++++++ .irqindex_host_ipc = 0, ++++++ .sst_id = SST_DEV_ID_WILDCAT_POINT, ++++++ .dma_engine = SST_DMA_TYPE_DW, ++++++ .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET, ++++++ .dma_size = SST_LPT_DSP_DMA_SIZE, ++++++}; ++++++ ++++++static struct sst_acpi_mach baytrail_machines[] = { ++++++ { "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-i2s_master" }, ++++++ {} ++++++}; ++++++ ++++++static struct sst_acpi_desc sst_acpi_baytrail_desc = { ++++++ .drv_name = "baytrail-pcm-audio", ++++++ .machines = baytrail_machines, ++++++ .resindex_lpe_base = 0, ++++++ .resindex_pcicfg_base = 1, ++++++ .resindex_fw_base = 2, ++++++ .irqindex_host_ipc = 5, ++++++ .sst_id = SST_DEV_ID_BYT, ++++++ .resindex_dma_base = -1, ++++++}; ++++++ ++++++static struct acpi_device_id sst_acpi_match[] = { ++++++ { "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, ++++++ { "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, ++++++ { "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, ++++++ { } ++++++}; ++++++MODULE_DEVICE_TABLE(acpi, sst_acpi_match); ++++++ ++++++static struct platform_driver sst_acpi_driver = { ++++++ .probe = sst_acpi_probe, ++++++ .remove = sst_acpi_remove, ++++++ .driver = { ++++++ .name = "sst-acpi", ++++++ .owner = THIS_MODULE, ++++++ .acpi_match_table = ACPI_PTR(sst_acpi_match), ++++++ }, ++++++}; ++++++module_platform_driver(sst_acpi_driver); ++++++ ++++++MODULE_AUTHOR("Jarkko Nikula "); ++++++MODULE_DESCRIPTION("Intel SST loader on ACPI systems"); ++++++MODULE_LICENSE("GPL v2"); diff --cc sound/soc/intel/sst-baytrail-dsp.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..a50bf7fc0e3a new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-dsp.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,372 @@@@@@@ ++++++/* ++++++ * Intel Baytrail SST DSP driver ++++++ * Copyright (c) 2014, Intel Corporation. ++++++ * ++++++ * This program is free software; you can redistribute it and/or modify it ++++++ * under the terms and conditions of the GNU General Public License, ++++++ * version 2, as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope it will be useful, but WITHOUT ++++++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++++++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++++++ * more details. ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++#include "sst-baytrail-ipc.h" ++++++ ++++++#define SST_BYT_FW_SIGNATURE_SIZE 4 ++++++#define SST_BYT_FW_SIGN "$SST" ++++++ ++++++#define SST_BYT_IRAM_OFFSET 0xC0000 ++++++#define SST_BYT_DRAM_OFFSET 0x100000 ++++++#define SST_BYT_SHIM_OFFSET 0x140000 ++++++ ++++++enum sst_ram_type { ++++++ SST_BYT_IRAM = 1, ++++++ SST_BYT_DRAM = 2, ++++++ SST_BYT_CACHE = 3, ++++++}; ++++++ ++++++struct dma_block_info { ++++++ enum sst_ram_type type; /* IRAM/DRAM */ ++++++ u32 size; /* Bytes */ ++++++ u32 ram_offset; /* Offset in I/DRAM */ ++++++ u32 rsvd; /* Reserved field */ ++++++}; ++++++ ++++++struct fw_header { ++++++ unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; ++++++ u32 file_size; /* size of fw minus this header */ ++++++ u32 modules; /* # of modules */ ++++++ u32 file_format; /* version of header format */ ++++++ u32 reserved[4]; ++++++}; ++++++ ++++++struct sst_byt_fw_module_header { ++++++ unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; ++++++ u32 mod_size; /* size of module */ ++++++ u32 blocks; /* # of blocks */ ++++++ u32 type; /* codec type, pp lib */ ++++++ u32 entry_point; ++++++}; ++++++ ++++++static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ++++++ struct sst_byt_fw_module_header *module) ++++++{ ++++++ struct dma_block_info *block; ++++++ struct sst_module *mod; ++++++ struct sst_module_data block_data; ++++++ struct sst_module_template template; ++++++ int count; ++++++ ++++++ memset(&template, 0, sizeof(template)); ++++++ template.id = module->type; ++++++ template.entry = module->entry_point; ++++++ template.p.type = SST_MEM_DRAM; ++++++ template.p.data_type = SST_DATA_P; ++++++ template.s.type = SST_MEM_DRAM; ++++++ template.s.data_type = SST_DATA_S; ++++++ ++++++ mod = sst_module_new(fw, &template, NULL); ++++++ if (mod == NULL) ++++++ return -ENOMEM; ++++++ ++++++ block = (void *)module + sizeof(*module); ++++++ ++++++ for (count = 0; count < module->blocks; count++) { ++++++ ++++++ if (block->size <= 0) { ++++++ dev_err(dsp->dev, "block %d size invalid\n", count); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ switch (block->type) { ++++++ case SST_BYT_IRAM: ++++++ block_data.offset = block->ram_offset + ++++++ dsp->addr.iram_offset; ++++++ block_data.type = SST_MEM_IRAM; ++++++ break; ++++++ case SST_BYT_DRAM: ++++++ block_data.offset = block->ram_offset + ++++++ dsp->addr.dram_offset; ++++++ block_data.type = SST_MEM_DRAM; ++++++ break; ++++++ case SST_BYT_CACHE: ++++++ block_data.offset = block->ram_offset + ++++++ (dsp->addr.fw_ext - dsp->addr.lpe); ++++++ block_data.type = SST_MEM_CACHE; ++++++ break; ++++++ default: ++++++ dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", ++++++ block->type, count); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ block_data.size = block->size; ++++++ block_data.data_type = SST_DATA_M; ++++++ block_data.data = (void *)block + sizeof(*block); ++++++ ++++++ sst_module_insert_fixed_block(mod, &block_data); ++++++ ++++++ block = (void *)block + sizeof(*block) + block->size; ++++++ } ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_parse_fw_image(struct sst_fw *sst_fw) ++++++{ ++++++ struct fw_header *header; ++++++ struct sst_byt_fw_module_header *module; ++++++ struct sst_dsp *dsp = sst_fw->dsp; ++++++ int ret, count; ++++++ ++++++ /* Read the header information from the data pointer */ ++++++ header = (struct fw_header *)sst_fw->dma_buf; ++++++ ++++++ /* verify FW */ ++++++ if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) || ++++++ (sst_fw->size != header->file_size + sizeof(*header))) { ++++++ /* Invalid FW signature */ ++++++ dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ dev_dbg(dsp->dev, ++++++ "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n", ++++++ header->signature, header->file_size, header->modules, ++++++ header->file_format, sizeof(*header)); ++++++ ++++++ module = (void *)sst_fw->dma_buf + sizeof(*header); ++++++ for (count = 0; count < header->modules; count++) { ++++++ /* module */ ++++++ ret = sst_byt_parse_module(dsp, sst_fw, module); ++++++ if (ret < 0) { ++++++ dev_err(dsp->dev, "invalid module %d\n", count); ++++++ return ret; ++++++ } ++++++ module = (void *)module + sizeof(*module) + module->mod_size; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static void sst_byt_dump_shim(struct sst_dsp *sst) ++++++{ ++++++ int i; ++++++ u64 reg; ++++++ ++++++ for (i = 0; i <= 0xF0; i += 8) { ++++++ reg = sst_dsp_shim_read64_unlocked(sst, i); ++++++ if (reg) ++++++ dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n", ++++++ i, reg); ++++++ } ++++++ ++++++ for (i = 0x00; i <= 0xff; i += 4) { ++++++ reg = readl(sst->addr.pci_cfg + i); ++++++ if (reg) ++++++ dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n", ++++++ i, (u32)reg); ++++++ } ++++++} ++++++ ++++++static irqreturn_t sst_byt_irq(int irq, void *context) ++++++{ ++++++ struct sst_dsp *sst = (struct sst_dsp *) context; ++++++ u64 isrx; ++++++ irqreturn_t ret = IRQ_NONE; ++++++ ++++++ spin_lock(&sst->spinlock); ++++++ ++++++ isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); ++++++ if (isrx & SST_ISRX_DONE) { ++++++ /* ADSP has processed the message request from IA */ ++++++ sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, ++++++ SST_BYT_IPCX_DONE, 0); ++++++ ret = IRQ_WAKE_THREAD; ++++++ } ++++++ if (isrx & SST_BYT_ISRX_REQUEST) { ++++++ /* mask message request from ADSP and do processing later */ ++++++ sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, ++++++ SST_BYT_IMRX_REQUEST, ++++++ SST_BYT_IMRX_REQUEST); ++++++ ret = IRQ_WAKE_THREAD; ++++++ } ++++++ ++++++ spin_unlock(&sst->spinlock); ++++++ ++++++ return ret; ++++++} ++++++ ++++++static void sst_byt_boot(struct sst_dsp *sst) ++++++{ ++++++ int tries = 10; ++++++ ++++++ /* release stall and wait to unstall */ ++++++ sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0); ++++++ while (tries--) { ++++++ if (!(sst_dsp_shim_read64(sst, SST_CSR) & ++++++ SST_BYT_CSR_PWAITMODE)) ++++++ break; ++++++ msleep(100); ++++++ } ++++++ if (tries < 0) { ++++++ dev_err(sst->dev, "unable to start DSP\n"); ++++++ sst_byt_dump_shim(sst); ++++++ } ++++++} ++++++ ++++++static void sst_byt_reset(struct sst_dsp *sst) ++++++{ ++++++ /* put DSP into reset, set reset vector and stall */ ++++++ sst_dsp_shim_update_bits64(sst, SST_CSR, ++++++ SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL, ++++++ SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL); ++++++ ++++++ udelay(10); ++++++ ++++++ /* take DSP out of reset and keep stalled for FW loading */ ++++++ sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0); ++++++} ++++++ ++++++struct sst_adsp_memregion { ++++++ u32 start; ++++++ u32 end; ++++++ int blocks; ++++++ enum sst_mem_type type; ++++++}; ++++++ ++++++/* BYT test stuff */ ++++++static const struct sst_adsp_memregion byt_region[] = { ++++++ {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */ ++++++ {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ ++++++}; ++++++ ++++++static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) ++++++{ ++++++ sst->addr.lpe_base = pdata->lpe_base; ++++++ sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); ++++++ if (!sst->addr.lpe) ++++++ return -ENODEV; ++++++ ++++++ /* ADSP PCI MMIO config space */ ++++++ sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); ++++++ if (!sst->addr.pci_cfg) { ++++++ iounmap(sst->addr.lpe); ++++++ return -ENODEV; ++++++ } ++++++ ++++++ /* SST Extended FW allocation */ ++++++ sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size); ++++++ if (!sst->addr.fw_ext) { ++++++ iounmap(sst->addr.pci_cfg); ++++++ iounmap(sst->addr.lpe); ++++++ return -ENODEV; ++++++ } ++++++ ++++++ /* SST Shim */ ++++++ sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; ++++++ ++++++ sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204, ++++++ SST_BYT_IPC_MAX_PAYLOAD_SIZE, ++++++ SST_BYT_MAILBOX_OFFSET, ++++++ SST_BYT_IPC_MAX_PAYLOAD_SIZE); ++++++ ++++++ sst->irq = pdata->irq; ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata) ++++++{ ++++++ const struct sst_adsp_memregion *region; ++++++ struct device *dev; ++++++ int ret = -ENODEV, i, j, region_count; ++++++ u32 offset, size; ++++++ ++++++ dev = sst->dev; ++++++ ++++++ switch (sst->id) { ++++++ case SST_DEV_ID_BYT: ++++++ region = byt_region; ++++++ region_count = ARRAY_SIZE(byt_region); ++++++ sst->addr.iram_offset = SST_BYT_IRAM_OFFSET; ++++++ sst->addr.dram_offset = SST_BYT_DRAM_OFFSET; ++++++ sst->addr.shim_offset = SST_BYT_SHIM_OFFSET; ++++++ break; ++++++ default: ++++++ dev_err(dev, "failed to get mem resources\n"); ++++++ return ret; ++++++ } ++++++ ++++++ ret = sst_byt_resource_map(sst, pdata); ++++++ if (ret < 0) { ++++++ dev_err(dev, "failed to map resources\n"); ++++++ return ret; ++++++ } ++++++ ++++++ /* ++++++ * save the physical address of extended firmware block in the first ++++++ * 4 bytes of the mailbox ++++++ */ ++++++ memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET, ++++++ &pdata->fw_base, sizeof(u32)); ++++++ ++++++ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); ++++++ if (ret) ++++++ return ret; ++++++ ++++++ /* enable Interrupt from both sides */ ++++++ sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0); ++++++ sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0); ++++++ ++++++ /* register DSP memory blocks - ideally we should get this from ACPI */ ++++++ for (i = 0; i < region_count; i++) { ++++++ offset = region[i].start; ++++++ size = (region[i].end - region[i].start) / region[i].blocks; ++++++ ++++++ /* register individual memory blocks */ ++++++ for (j = 0; j < region[i].blocks; j++) { ++++++ sst_mem_block_register(sst, offset, size, ++++++ region[i].type, NULL, j, sst); ++++++ offset += size; ++++++ } ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static void sst_byt_free(struct sst_dsp *sst) ++++++{ ++++++ sst_mem_block_unregister_all(sst); ++++++ iounmap(sst->addr.lpe); ++++++ iounmap(sst->addr.pci_cfg); ++++++ iounmap(sst->addr.fw_ext); ++++++} ++++++ ++++++struct sst_ops sst_byt_ops = { ++++++ .reset = sst_byt_reset, ++++++ .boot = sst_byt_boot, ++++++ .write = sst_shim32_write, ++++++ .read = sst_shim32_read, ++++++ .write64 = sst_shim32_write64, ++++++ .read64 = sst_shim32_read64, ++++++ .ram_read = sst_memcpy_fromio_32, ++++++ .ram_write = sst_memcpy_toio_32, ++++++ .irq_handler = sst_byt_irq, ++++++ .init = sst_byt_init, ++++++ .free = sst_byt_free, ++++++ .parse_fw = sst_byt_parse_fw_image, ++++++}; diff --cc sound/soc/intel/sst-baytrail-ipc.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..d0eaeee21be4 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-ipc.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,867 @@@@@@@ ++++++/* ++++++ * Intel Baytrail SST IPC Support ++++++ * Copyright (c) 2014, Intel Corporation. ++++++ * ++++++ * This program is free software; you can redistribute it and/or modify it ++++++ * under the terms and conditions of the GNU General Public License, ++++++ * version 2, as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope it will be useful, but WITHOUT ++++++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++++++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++++++ * more details. ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-baytrail-ipc.h" ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++ ++++++/* IPC message timeout */ ++++++#define IPC_TIMEOUT_MSECS 300 ++++++#define IPC_BOOT_MSECS 200 ++++++ ++++++#define IPC_EMPTY_LIST_SIZE 8 ++++++ ++++++/* IPC header bits */ ++++++#define IPC_HEADER_MSG_ID_MASK 0xff ++++++#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK) ++++++#define IPC_HEADER_STR_ID_SHIFT 8 ++++++#define IPC_HEADER_STR_ID_MASK 0x1f ++++++#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT) ++++++#define IPC_HEADER_LARGE_SHIFT 13 ++++++#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT) ++++++#define IPC_HEADER_DATA_SHIFT 16 ++++++#define IPC_HEADER_DATA_MASK 0x3fff ++++++#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT) ++++++ ++++++/* mask for differentiating between notification and reply message */ ++++++#define IPC_NOTIFICATION (0x1 << 7) ++++++ ++++++/* I2L Stream config/control msgs */ ++++++#define IPC_IA_ALLOC_STREAM 0x20 ++++++#define IPC_IA_FREE_STREAM 0x21 ++++++#define IPC_IA_PAUSE_STREAM 0x24 ++++++#define IPC_IA_RESUME_STREAM 0x25 ++++++#define IPC_IA_DROP_STREAM 0x26 ++++++#define IPC_IA_START_STREAM 0x30 ++++++ ++++++/* notification messages */ ++++++#define IPC_IA_FW_INIT_CMPLT 0x81 ++++++#define IPC_SST_PERIOD_ELAPSED 0x97 ++++++ ++++++/* IPC messages between host and ADSP */ ++++++struct sst_byt_address_info { ++++++ u32 addr; ++++++ u32 size; ++++++} __packed; ++++++ ++++++struct sst_byt_str_type { ++++++ u8 codec_type; ++++++ u8 str_type; ++++++ u8 operation; ++++++ u8 protected_str; ++++++ u8 time_slots; ++++++ u8 reserved; ++++++ u16 result; ++++++} __packed; ++++++ ++++++struct sst_byt_pcm_params { ++++++ u8 num_chan; ++++++ u8 pcm_wd_sz; ++++++ u8 use_offload_path; ++++++ u8 reserved; ++++++ u32 sfreq; ++++++ u8 channel_map[8]; ++++++} __packed; ++++++ ++++++struct sst_byt_frames_info { ++++++ u16 num_entries; ++++++ u16 rsrvd; ++++++ u32 frag_size; ++++++ struct sst_byt_address_info ring_buf_info[8]; ++++++} __packed; ++++++ ++++++struct sst_byt_alloc_params { ++++++ struct sst_byt_str_type str_type; ++++++ struct sst_byt_pcm_params pcm_params; ++++++ struct sst_byt_frames_info frame_info; ++++++} __packed; ++++++ ++++++struct sst_byt_alloc_response { ++++++ struct sst_byt_str_type str_type; ++++++ u8 reserved[88]; ++++++} __packed; ++++++ ++++++struct sst_byt_start_stream_params { ++++++ u32 byte_offset; ++++++} __packed; ++++++ ++++++struct sst_byt_tstamp { ++++++ u64 ring_buffer_counter; ++++++ u64 hardware_counter; ++++++ u64 frames_decoded; ++++++ u64 bytes_decoded; ++++++ u64 bytes_copied; ++++++ u32 sampling_frequency; ++++++ u32 channel_peak[8]; ++++++} __packed; ++++++ ++++++/* driver internal IPC message structure */ ++++++struct ipc_message { ++++++ struct list_head list; ++++++ u64 header; ++++++ ++++++ /* direction wrt host CPU */ ++++++ char tx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; ++++++ size_t tx_size; ++++++ char rx_data[SST_BYT_IPC_MAX_PAYLOAD_SIZE]; ++++++ size_t rx_size; ++++++ ++++++ wait_queue_head_t waitq; ++++++ bool complete; ++++++ bool wait; ++++++ int errno; ++++++}; ++++++ ++++++struct sst_byt_stream; ++++++struct sst_byt; ++++++ ++++++/* stream infomation */ ++++++struct sst_byt_stream { ++++++ struct list_head node; ++++++ ++++++ /* configuration */ ++++++ struct sst_byt_alloc_params request; ++++++ struct sst_byt_alloc_response reply; ++++++ ++++++ /* runtime info */ ++++++ struct sst_byt *byt; ++++++ int str_id; ++++++ bool commited; ++++++ bool running; ++++++ ++++++ /* driver callback */ ++++++ u32 (*notify_position)(struct sst_byt_stream *stream, void *data); ++++++ void *pdata; ++++++}; ++++++ ++++++/* SST Baytrail IPC data */ ++++++struct sst_byt { ++++++ struct device *dev; ++++++ struct sst_dsp *dsp; ++++++ ++++++ /* stream */ ++++++ struct list_head stream_list; ++++++ ++++++ /* boot */ ++++++ wait_queue_head_t boot_wait; ++++++ bool boot_complete; ++++++ ++++++ /* IPC messaging */ ++++++ struct list_head tx_list; ++++++ struct list_head rx_list; ++++++ struct list_head empty_list; ++++++ wait_queue_head_t wait_txq; ++++++ struct task_struct *tx_thread; ++++++ struct kthread_worker kworker; ++++++ struct kthread_work kwork; ++++++ struct ipc_message *msg; ++++++}; ++++++ ++++++static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) ++++++{ ++++++ u64 header; ++++++ ++++++ header = IPC_HEADER_MSG_ID(msg_id) | ++++++ IPC_HEADER_STR_ID(str_id) | ++++++ IPC_HEADER_LARGE(large) | ++++++ IPC_HEADER_DATA(data) | ++++++ SST_BYT_IPCX_BUSY; ++++++ ++++++ return header; ++++++} ++++++ ++++++static inline u16 sst_byt_header_msg_id(u64 header) ++++++{ ++++++ return header & IPC_HEADER_MSG_ID_MASK; ++++++} ++++++ ++++++static inline u8 sst_byt_header_str_id(u64 header) ++++++{ ++++++ return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK; ++++++} ++++++ ++++++static inline u16 sst_byt_header_data(u64 header) ++++++{ ++++++ return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK; ++++++} ++++++ ++++++static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, ++++++ int stream_id) ++++++{ ++++++ struct sst_byt_stream *stream; ++++++ ++++++ list_for_each_entry(stream, &byt->stream_list, node) { ++++++ if (stream->str_id == stream_id) ++++++ return stream; ++++++ } ++++++ ++++++ return NULL; ++++++} ++++++ ++++++static void sst_byt_ipc_shim_dbg(struct sst_byt *byt, const char *text) ++++++{ ++++++ struct sst_dsp *sst = byt->dsp; ++++++ u64 isr, ipcd, imrx, ipcx; ++++++ ++++++ ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); ++++++ isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); ++++++ ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); ++++++ imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); ++++++ ++++++ dev_err(byt->dev, ++++++ "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", ++++++ text, ipcx, isr, ipcd, imrx); ++++++} ++++++ ++++++/* locks held by caller */ ++++++static struct ipc_message *sst_byt_msg_get_empty(struct sst_byt *byt) ++++++{ ++++++ struct ipc_message *msg = NULL; ++++++ ++++++ if (!list_empty(&byt->empty_list)) { ++++++ msg = list_first_entry(&byt->empty_list, ++++++ struct ipc_message, list); ++++++ list_del(&msg->list); ++++++ } ++++++ ++++++ return msg; ++++++} ++++++ ++++++static void sst_byt_ipc_tx_msgs(struct kthread_work *work) ++++++{ ++++++ struct sst_byt *byt = ++++++ container_of(work, struct sst_byt, kwork); ++++++ struct ipc_message *msg; ++++++ u64 ipcx; ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&byt->dsp->spinlock, flags); ++++++ if (list_empty(&byt->tx_list)) { ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++ return; ++++++ } ++++++ ++++++ /* if the DSP is busy we will TX messages after IRQ */ ++++++ ipcx = sst_dsp_shim_read64_unlocked(byt->dsp, SST_IPCX); ++++++ if (ipcx & SST_BYT_IPCX_BUSY) { ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++ return; ++++++ } ++++++ ++++++ msg = list_first_entry(&byt->tx_list, struct ipc_message, list); ++++++ ++++++ list_move(&msg->list, &byt->rx_list); ++++++ ++++++ /* send the message */ ++++++ if (msg->header & IPC_HEADER_LARGE(true)) ++++++ sst_dsp_outbox_write(byt->dsp, msg->tx_data, msg->tx_size); ++++++ sst_dsp_shim_write64_unlocked(byt->dsp, SST_IPCX, msg->header); ++++++ ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++} ++++++ ++++++static inline void sst_byt_tx_msg_reply_complete(struct sst_byt *byt, ++++++ struct ipc_message *msg) ++++++{ ++++++ msg->complete = true; ++++++ ++++++ if (!msg->wait) ++++++ list_add_tail(&msg->list, &byt->empty_list); ++++++ else ++++++ wake_up(&msg->waitq); ++++++} ++++++ ++++++static int sst_byt_tx_wait_done(struct sst_byt *byt, struct ipc_message *msg, ++++++ void *rx_data) ++++++{ ++++++ unsigned long flags; ++++++ int ret; ++++++ ++++++ /* wait for DSP completion */ ++++++ ret = wait_event_timeout(msg->waitq, msg->complete, ++++++ msecs_to_jiffies(IPC_TIMEOUT_MSECS)); ++++++ ++++++ spin_lock_irqsave(&byt->dsp->spinlock, flags); ++++++ if (ret == 0) { ++++++ list_del(&msg->list); ++++++ sst_byt_ipc_shim_dbg(byt, "message timeout"); ++++++ ++++++ ret = -ETIMEDOUT; ++++++ } else { ++++++ ++++++ /* copy the data returned from DSP */ ++++++ if (msg->rx_size) ++++++ memcpy(rx_data, msg->rx_data, msg->rx_size); ++++++ ret = msg->errno; ++++++ } ++++++ ++++++ list_add_tail(&msg->list, &byt->empty_list); ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++ return ret; ++++++} ++++++ ++++++static int sst_byt_ipc_tx_message(struct sst_byt *byt, u64 header, ++++++ void *tx_data, size_t tx_bytes, ++++++ void *rx_data, size_t rx_bytes, int wait) ++++++{ ++++++ unsigned long flags; ++++++ struct ipc_message *msg; ++++++ ++++++ spin_lock_irqsave(&byt->dsp->spinlock, flags); ++++++ ++++++ msg = sst_byt_msg_get_empty(byt); ++++++ if (msg == NULL) { ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++ return -EBUSY; ++++++ } ++++++ ++++++ msg->header = header; ++++++ msg->tx_size = tx_bytes; ++++++ msg->rx_size = rx_bytes; ++++++ msg->wait = wait; ++++++ msg->errno = 0; ++++++ msg->complete = false; ++++++ ++++++ if (tx_bytes) { ++++++ /* msg content = lower 32-bit of the header + data */ ++++++ *(u32 *)msg->tx_data = (u32)(header & (u32)-1); ++++++ memcpy(msg->tx_data + sizeof(u32), tx_data, tx_bytes); ++++++ msg->tx_size += sizeof(u32); ++++++ } ++++++ ++++++ list_add_tail(&msg->list, &byt->tx_list); ++++++ spin_unlock_irqrestore(&byt->dsp->spinlock, flags); ++++++ ++++++ queue_kthread_work(&byt->kworker, &byt->kwork); ++++++ ++++++ if (wait) ++++++ return sst_byt_tx_wait_done(byt, msg, rx_data); ++++++ else ++++++ return 0; ++++++} ++++++ ++++++static inline int sst_byt_ipc_tx_msg_wait(struct sst_byt *byt, u64 header, ++++++ void *tx_data, size_t tx_bytes, ++++++ void *rx_data, size_t rx_bytes) ++++++{ ++++++ return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, ++++++ rx_data, rx_bytes, 1); ++++++} ++++++ ++++++static inline int sst_byt_ipc_tx_msg_nowait(struct sst_byt *byt, u64 header, ++++++ void *tx_data, size_t tx_bytes) ++++++{ ++++++ return sst_byt_ipc_tx_message(byt, header, tx_data, tx_bytes, ++++++ NULL, 0, 0); ++++++} ++++++ ++++++static struct ipc_message *sst_byt_reply_find_msg(struct sst_byt *byt, ++++++ u64 header) ++++++{ ++++++ struct ipc_message *msg = NULL, *_msg; ++++++ u64 mask; ++++++ ++++++ /* match reply to message sent based on msg and stream IDs */ ++++++ mask = IPC_HEADER_MSG_ID_MASK | ++++++ IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; ++++++ header &= mask; ++++++ ++++++ if (list_empty(&byt->rx_list)) { ++++++ dev_err(byt->dev, ++++++ "ipc: rx list is empty but received 0x%llx\n", header); ++++++ goto out; ++++++ } ++++++ ++++++ list_for_each_entry(_msg, &byt->rx_list, list) { ++++++ if ((_msg->header & mask) == header) { ++++++ msg = _msg; ++++++ break; ++++++ } ++++++ } ++++++ ++++++out: ++++++ return msg; ++++++} ++++++ ++++++static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) ++++++{ ++++++ struct sst_byt_stream *stream; ++++++ u64 header = msg->header; ++++++ u8 stream_id = sst_byt_header_str_id(header); ++++++ u8 stream_msg = sst_byt_header_msg_id(header); ++++++ ++++++ stream = sst_byt_get_stream(byt, stream_id); ++++++ if (stream == NULL) ++++++ return; ++++++ ++++++ switch (stream_msg) { ++++++ case IPC_IA_DROP_STREAM: ++++++ case IPC_IA_PAUSE_STREAM: ++++++ case IPC_IA_FREE_STREAM: ++++++ stream->running = false; ++++++ break; ++++++ case IPC_IA_START_STREAM: ++++++ case IPC_IA_RESUME_STREAM: ++++++ stream->running = true; ++++++ break; ++++++ } ++++++} ++++++ ++++++static int sst_byt_process_reply(struct sst_byt *byt, u64 header) ++++++{ ++++++ struct ipc_message *msg; ++++++ ++++++ msg = sst_byt_reply_find_msg(byt, header); ++++++ if (msg == NULL) ++++++ return 1; ++++++ ++++++ if (header & IPC_HEADER_LARGE(true)) { ++++++ msg->rx_size = sst_byt_header_data(header); ++++++ sst_dsp_inbox_read(byt->dsp, msg->rx_data, msg->rx_size); ++++++ } ++++++ ++++++ /* update any stream states */ ++++++ sst_byt_stream_update(byt, msg); ++++++ ++++++ list_del(&msg->list); ++++++ /* wake up */ ++++++ sst_byt_tx_msg_reply_complete(byt, msg); ++++++ ++++++ return 1; ++++++} ++++++ ++++++static void sst_byt_fw_ready(struct sst_byt *byt, u64 header) ++++++{ ++++++ dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header); ++++++ ++++++ byt->boot_complete = true; ++++++ wake_up(&byt->boot_wait); ++++++} ++++++ ++++++static int sst_byt_process_notification(struct sst_byt *byt, ++++++ unsigned long *flags) ++++++{ ++++++ struct sst_dsp *sst = byt->dsp; ++++++ struct sst_byt_stream *stream; ++++++ u64 header; ++++++ u8 msg_id, stream_id; ++++++ int handled = 1; ++++++ ++++++ header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); ++++++ msg_id = sst_byt_header_msg_id(header); ++++++ ++++++ switch (msg_id) { ++++++ case IPC_SST_PERIOD_ELAPSED: ++++++ stream_id = sst_byt_header_str_id(header); ++++++ stream = sst_byt_get_stream(byt, stream_id); ++++++ if (stream && stream->running && stream->notify_position) { ++++++ spin_unlock_irqrestore(&sst->spinlock, *flags); ++++++ stream->notify_position(stream, stream->pdata); ++++++ spin_lock_irqsave(&sst->spinlock, *flags); ++++++ } ++++++ break; ++++++ case IPC_IA_FW_INIT_CMPLT: ++++++ sst_byt_fw_ready(byt, header); ++++++ break; ++++++ } ++++++ ++++++ return handled; ++++++} ++++++ ++++++static irqreturn_t sst_byt_irq_thread(int irq, void *context) ++++++{ ++++++ struct sst_dsp *sst = (struct sst_dsp *) context; ++++++ struct sst_byt *byt = sst_dsp_get_thread_context(sst); ++++++ u64 header; ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ ++++++ header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); ++++++ if (header & SST_BYT_IPCD_BUSY) { ++++++ if (header & IPC_NOTIFICATION) { ++++++ /* message from ADSP */ ++++++ sst_byt_process_notification(byt, &flags); ++++++ } else { ++++++ /* reply from ADSP */ ++++++ sst_byt_process_reply(byt, header); ++++++ } ++++++ /* ++++++ * clear IPCD BUSY bit and set DONE bit. Tell DSP we have ++++++ * processed the message and can accept new. Clear data part ++++++ * of the header ++++++ */ ++++++ sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, ++++++ SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY | ++++++ IPC_HEADER_DATA(IPC_HEADER_DATA_MASK), ++++++ SST_BYT_IPCD_DONE); ++++++ /* unmask message request interrupts */ ++++++ sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, ++++++ SST_BYT_IMRX_REQUEST, 0); ++++++ } ++++++ ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ ++++++ /* continue to send any remaining messages... */ ++++++ queue_kthread_work(&byt->kworker, &byt->kwork); ++++++ ++++++ return IRQ_HANDLED; ++++++} ++++++ ++++++/* stream API */ ++++++struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, ++++++ u32 (*notify_position)(struct sst_byt_stream *stream, void *data), ++++++ void *data) ++++++{ ++++++ struct sst_byt_stream *stream; ++++++ ++++++ stream = kzalloc(sizeof(*stream), GFP_KERNEL); ++++++ if (stream == NULL) ++++++ return NULL; ++++++ ++++++ list_add(&stream->node, &byt->stream_list); ++++++ stream->notify_position = notify_position; ++++++ stream->pdata = data; ++++++ stream->byt = byt; ++++++ stream->str_id = id; ++++++ ++++++ return stream; ++++++} ++++++ ++++++int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ int bits) ++++++{ ++++++ stream->request.pcm_params.pcm_wd_sz = bits; ++++++ return 0; ++++++} ++++++ ++++++int sst_byt_stream_set_channels(struct sst_byt *byt, ++++++ struct sst_byt_stream *stream, u8 channels) ++++++{ ++++++ stream->request.pcm_params.num_chan = channels; ++++++ return 0; ++++++} ++++++ ++++++int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ unsigned int rate) ++++++{ ++++++ stream->request.pcm_params.sfreq = rate; ++++++ return 0; ++++++} ++++++ ++++++/* stream sonfiguration */ ++++++int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ int codec_type, int stream_type, int operation) ++++++{ ++++++ stream->request.str_type.codec_type = codec_type; ++++++ stream->request.str_type.str_type = stream_type; ++++++ stream->request.str_type.operation = operation; ++++++ stream->request.str_type.time_slots = 0xc; ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ uint32_t buffer_addr, uint32_t buffer_size) ++++++{ ++++++ stream->request.frame_info.num_entries = 1; ++++++ stream->request.frame_info.ring_buf_info[0].addr = buffer_addr; ++++++ stream->request.frame_info.ring_buf_info[0].size = buffer_size; ++++++ /* calculate bytes per 4 ms fragment */ ++++++ stream->request.frame_info.frag_size = ++++++ stream->request.pcm_params.sfreq * ++++++ stream->request.pcm_params.num_chan * ++++++ stream->request.pcm_params.pcm_wd_sz / 8 * ++++++ 4 / 1000; ++++++ return 0; ++++++} ++++++ ++++++int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ struct sst_byt_alloc_params *str_req = &stream->request; ++++++ struct sst_byt_alloc_response *reply = &stream->reply; ++++++ u64 header; ++++++ int ret; ++++++ ++++++ header = sst_byt_header(IPC_IA_ALLOC_STREAM, ++++++ sizeof(*str_req) + sizeof(u32), ++++++ true, stream->str_id); ++++++ ret = sst_byt_ipc_tx_msg_wait(byt, header, str_req, sizeof(*str_req), ++++++ reply, sizeof(*reply)); ++++++ if (ret < 0) { ++++++ dev_err(byt->dev, "ipc: error stream commit failed\n"); ++++++ return ret; ++++++ } ++++++ ++++++ stream->commited = true; ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ u64 header; ++++++ int ret = 0; ++++++ ++++++ if (!stream->commited) ++++++ goto out; ++++++ ++++++ header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); ++++++ ret = sst_byt_ipc_tx_msg_wait(byt, header, NULL, 0, NULL, 0); ++++++ if (ret < 0) { ++++++ dev_err(byt->dev, "ipc: free stream %d failed\n", ++++++ stream->str_id); ++++++ return -EAGAIN; ++++++ } ++++++ ++++++ stream->commited = false; ++++++out: ++++++ list_del(&stream->node); ++++++ kfree(stream); ++++++ ++++++ return ret; ++++++} ++++++ ++++++static int sst_byt_stream_operations(struct sst_byt *byt, int type, ++++++ int stream_id, int wait) ++++++{ ++++++ struct sst_byt_start_stream_params start_stream; ++++++ u64 header; ++++++ void *tx_msg = NULL; ++++++ size_t size = 0; ++++++ ++++++ if (type != IPC_IA_START_STREAM) { ++++++ header = sst_byt_header(type, 0, false, stream_id); ++++++ } else { ++++++ start_stream.byte_offset = 0; ++++++ header = sst_byt_header(IPC_IA_START_STREAM, ++++++ sizeof(start_stream) + sizeof(u32), ++++++ true, stream_id); ++++++ tx_msg = &start_stream; ++++++ size = sizeof(start_stream); ++++++ } ++++++ ++++++ if (wait) ++++++ return sst_byt_ipc_tx_msg_wait(byt, header, ++++++ tx_msg, size, NULL, 0); ++++++ else ++++++ return sst_byt_ipc_tx_msg_nowait(byt, header, tx_msg, size); ++++++} ++++++ ++++++/* stream ALSA trigger operations */ ++++++int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_byt_stream_operations(byt, IPC_IA_START_STREAM, ++++++ stream->str_id, 0); ++++++ if (ret < 0) ++++++ dev_err(byt->dev, "ipc: error failed to start stream %d\n", ++++++ stream->str_id); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ int ret; ++++++ ++++++ /* don't stop streams that are not commited */ ++++++ if (!stream->commited) ++++++ return 0; ++++++ ++++++ ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM, ++++++ stream->str_id, 0); ++++++ if (ret < 0) ++++++ dev_err(byt->dev, "ipc: error failed to stop stream %d\n", ++++++ stream->str_id); ++++++ return ret; ++++++} ++++++ ++++++int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM, ++++++ stream->str_id, 0); ++++++ if (ret < 0) ++++++ dev_err(byt->dev, "ipc: error failed to pause stream %d\n", ++++++ stream->str_id); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM, ++++++ stream->str_id, 0); ++++++ if (ret < 0) ++++++ dev_err(byt->dev, "ipc: error failed to resume stream %d\n", ++++++ stream->str_id); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_byt_get_dsp_position(struct sst_byt *byt, ++++++ struct sst_byt_stream *stream, int buffer_size) ++++++{ ++++++ struct sst_dsp *sst = byt->dsp; ++++++ struct sst_byt_tstamp fw_tstamp; ++++++ u8 str_id = stream->str_id; ++++++ u32 tstamp_offset; ++++++ ++++++ tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp); ++++++ memcpy_fromio(&fw_tstamp, ++++++ sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp)); ++++++ ++++++ return do_div(fw_tstamp.ring_buffer_counter, buffer_size); ++++++} ++++++ ++++++static int msg_empty_list_init(struct sst_byt *byt) ++++++{ ++++++ struct ipc_message *msg; ++++++ int i; ++++++ ++++++ byt->msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); ++++++ if (byt->msg == NULL) ++++++ return -ENOMEM; ++++++ ++++++ for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { ++++++ init_waitqueue_head(&byt->msg[i].waitq); ++++++ list_add(&byt->msg[i].list, &byt->empty_list); ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) ++++++{ ++++++ return byt->dsp; ++++++} ++++++ ++++++static struct sst_dsp_device byt_dev = { ++++++ .thread = sst_byt_irq_thread, ++++++ .ops = &sst_byt_ops, ++++++}; ++++++ ++++++int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) ++++++{ ++++++ struct sst_byt *byt; ++++++ struct sst_fw *byt_sst_fw; ++++++ int err; ++++++ ++++++ dev_dbg(dev, "initialising Byt DSP IPC\n"); ++++++ ++++++ byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL); ++++++ if (byt == NULL) ++++++ return -ENOMEM; ++++++ ++++++ byt->dev = dev; ++++++ INIT_LIST_HEAD(&byt->stream_list); ++++++ INIT_LIST_HEAD(&byt->tx_list); ++++++ INIT_LIST_HEAD(&byt->rx_list); ++++++ INIT_LIST_HEAD(&byt->empty_list); ++++++ init_waitqueue_head(&byt->boot_wait); ++++++ init_waitqueue_head(&byt->wait_txq); ++++++ ++++++ err = msg_empty_list_init(byt); ++++++ if (err < 0) ++++++ return -ENOMEM; ++++++ ++++++ /* start the IPC message thread */ ++++++ init_kthread_worker(&byt->kworker); ++++++ byt->tx_thread = kthread_run(kthread_worker_fn, ++++++ &byt->kworker, ++++++ dev_name(byt->dev)); ++++++ if (IS_ERR(byt->tx_thread)) { ++++++ err = PTR_ERR(byt->tx_thread); ++++++ dev_err(byt->dev, "error failed to create message TX task\n"); ++++++ goto err_free_msg; ++++++ } ++++++ init_kthread_work(&byt->kwork, sst_byt_ipc_tx_msgs); ++++++ ++++++ byt_dev.thread_context = byt; ++++++ ++++++ /* init SST shim */ ++++++ byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); ++++++ if (byt->dsp == NULL) { ++++++ err = -ENODEV; ++++++ goto err_free_msg; ++++++ } ++++++ ++++++ /* keep the DSP in reset state for base FW loading */ ++++++ sst_dsp_reset(byt->dsp); ++++++ ++++++ byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt); ++++++ if (byt_sst_fw == NULL) { ++++++ err = -ENODEV; ++++++ dev_err(dev, "error: failed to load firmware\n"); ++++++ goto fw_err; ++++++ } ++++++ ++++++ /* wait for DSP boot completion */ ++++++ sst_dsp_boot(byt->dsp); ++++++ err = wait_event_timeout(byt->boot_wait, byt->boot_complete, ++++++ msecs_to_jiffies(IPC_BOOT_MSECS)); ++++++ if (err == 0) { ++++++ err = -EIO; ++++++ dev_err(byt->dev, "ipc: error DSP boot timeout\n"); ++++++ goto boot_err; ++++++ } ++++++ ++++++ pdata->dsp = byt; ++++++ ++++++ return 0; ++++++ ++++++boot_err: ++++++ sst_dsp_reset(byt->dsp); ++++++ sst_fw_free(byt_sst_fw); ++++++fw_err: ++++++ sst_dsp_free(byt->dsp); ++++++err_free_msg: ++++++ kfree(byt->msg); ++++++ ++++++ return err; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_byt_dsp_init); ++++++ ++++++void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) ++++++{ ++++++ struct sst_byt *byt = pdata->dsp; ++++++ ++++++ sst_dsp_reset(byt->dsp); ++++++ sst_fw_free_all(byt->dsp); ++++++ sst_dsp_free(byt->dsp); ++++++ kfree(byt->msg); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --cc sound/soc/intel/sst-baytrail-ipc.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..f172b6440fa9 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-ipc.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,69 @@@@@@@ ++++++/* ++++++ * Intel Baytrail SST IPC Support ++++++ * Copyright (c) 2014, Intel Corporation. ++++++ * ++++++ * This program is free software; you can redistribute it and/or modify it ++++++ * under the terms and conditions of the GNU General Public License, ++++++ * version 2, as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope it will be useful, but WITHOUT ++++++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++++++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++++++ * more details. ++++++ */ ++++++ ++++++#ifndef __SST_BYT_IPC_H ++++++#define __SST_BYT_IPC_H ++++++ ++++++#include ++++++ ++++++struct sst_byt; ++++++struct sst_byt_stream; ++++++struct sst_pdata; ++++++extern struct sst_ops sst_byt_ops; ++++++ ++++++ ++++++#define SST_BYT_MAILBOX_OFFSET 0x144000 ++++++#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800) ++++++ ++++++/** ++++++ * Upfront defined maximum message size that is ++++++ * expected by the in/out communication pipes in FW. ++++++ */ ++++++#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200 ++++++ ++++++/* stream API */ ++++++struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, ++++++ uint32_t (*get_write_position)(struct sst_byt_stream *stream, ++++++ void *data), ++++++ void *data); ++++++ ++++++/* stream configuration */ ++++++int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ int bits); ++++++int sst_byt_stream_set_channels(struct sst_byt *byt, ++++++ struct sst_byt_stream *stream, u8 channels); ++++++int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ unsigned int rate); ++++++int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ int codec_type, int stream_type, int operation); ++++++int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, ++++++ uint32_t buffer_addr, uint32_t buffer_size); ++++++int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++ ++++++/* stream ALSA trigger operations */ ++++++int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream); ++++++ ++++++int sst_byt_get_dsp_position(struct sst_byt *byt, ++++++ struct sst_byt_stream *stream, int buffer_size); ++++++ ++++++/* init */ ++++++int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata); ++++++void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata); ++++++struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt); ++++++ ++++++#endif diff --cc sound/soc/intel/sst-baytrail-pcm.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..6d101f3813b4 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-baytrail-pcm.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,422 @@@@@@@ ++++++/* ++++++ * Intel Baytrail SST PCM Support ++++++ * Copyright (c) 2014, Intel Corporation. ++++++ * ++++++ * This program is free software; you can redistribute it and/or modify it ++++++ * under the terms and conditions of the GNU General Public License, ++++++ * version 2, as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope it will be useful, but WITHOUT ++++++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++++++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++++++ * more details. ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include "sst-baytrail-ipc.h" ++++++#include "sst-dsp-priv.h" ++++++#include "sst-dsp.h" ++++++ ++++++#define BYT_PCM_COUNT 2 ++++++ ++++++static const struct snd_pcm_hardware sst_byt_pcm_hardware = { ++++++ .info = SNDRV_PCM_INFO_MMAP | ++++++ SNDRV_PCM_INFO_MMAP_VALID | ++++++ SNDRV_PCM_INFO_INTERLEAVED | ++++++ SNDRV_PCM_INFO_PAUSE | ++++++ SNDRV_PCM_INFO_RESUME, ++++++ .formats = SNDRV_PCM_FMTBIT_S16_LE | ++++++ SNDRV_PCM_FORMAT_S24_LE, ++++++ .period_bytes_min = 384, ++++++ .period_bytes_max = 48000, ++++++ .periods_min = 2, ++++++ .periods_max = 250, ++++++ .buffer_bytes_max = 96000, ++++++}; ++++++ ++++++/* private data for each PCM DSP stream */ ++++++struct sst_byt_pcm_data { ++++++ struct sst_byt_stream *stream; ++++++ struct snd_pcm_substream *substream; ++++++ struct mutex mutex; ++++++}; ++++++ ++++++/* private data for the driver */ ++++++struct sst_byt_priv_data { ++++++ /* runtime DSP */ ++++++ struct sst_byt *byt; ++++++ ++++++ /* DAI data */ ++++++ struct sst_byt_pcm_data pcm[BYT_PCM_COUNT]; ++++++}; ++++++ ++++++/* this may get called several times by oss emulation */ ++++++static int sst_byt_pcm_hw_params(struct snd_pcm_substream *substream, ++++++ struct snd_pcm_hw_params *params) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct sst_byt_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct sst_byt_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_byt *byt = pdata->byt; ++++++ u32 rate, bits; ++++++ u8 channels; ++++++ int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); ++++++ ++++++ dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data); ++++++ ++++++ ret = sst_byt_stream_type(byt, pcm_data->stream, ++++++ 1, 1, !playback); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "failed to set stream format %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ rate = params_rate(params); ++++++ ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "could not set rate %d\n", rate); ++++++ return ret; ++++++ } ++++++ ++++++ bits = snd_pcm_format_width(params_format(params)); ++++++ ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "could not set formats %d\n", ++++++ params_rate(params)); ++++++ return ret; ++++++ } ++++++ ++++++ channels = (u8)(params_channels(params) & 0xF); ++++++ ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "could not set channels %d\n", ++++++ params_rate(params)); ++++++ return ret; ++++++ } ++++++ ++++++ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); ++++++ ++++++ ret = sst_byt_stream_buffer(byt, pcm_data->stream, ++++++ substream->dma_buffer.addr, ++++++ params_buffer_bytes(params)); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ ret = sst_byt_stream_commit(byt, pcm_data->stream); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_pcm_hw_free(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ ++++++ dev_dbg(rtd->dev, "PCM: hw_free\n"); ++++++ snd_pcm_lib_free_pages(substream); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct sst_byt_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct sst_byt_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_byt *byt = pdata->byt; ++++++ ++++++ dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd); ++++++ ++++++ switch (cmd) { ++++++ case SNDRV_PCM_TRIGGER_START: ++++++ sst_byt_stream_start(byt, pcm_data->stream); ++++++ break; ++++++ case SNDRV_PCM_TRIGGER_RESUME: ++++++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++++++ sst_byt_stream_resume(byt, pcm_data->stream); ++++++ break; ++++++ case SNDRV_PCM_TRIGGER_STOP: ++++++ sst_byt_stream_stop(byt, pcm_data->stream); ++++++ break; ++++++ case SNDRV_PCM_TRIGGER_SUSPEND: ++++++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++++++ sst_byt_stream_pause(byt, pcm_data->stream); ++++++ break; ++++++ default: ++++++ break; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data) ++++++{ ++++++ struct sst_byt_pcm_data *pcm_data = data; ++++++ struct snd_pcm_substream *substream = pcm_data->substream; ++++++ struct snd_pcm_runtime *runtime = substream->runtime; ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ u32 pos; ++++++ ++++++ pos = frames_to_bytes(runtime, ++++++ (runtime->control->appl_ptr % ++++++ runtime->buffer_size)); ++++++ ++++++ dev_dbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); ++++++ ++++++ snd_pcm_period_elapsed(substream); ++++++ return pos; ++++++} ++++++ ++++++static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct snd_pcm_runtime *runtime = substream->runtime; ++++++ struct sst_byt_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct sst_byt_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_byt *byt = pdata->byt; ++++++ snd_pcm_uframes_t offset; ++++++ int pos; ++++++ ++++++ pos = sst_byt_get_dsp_position(byt, pcm_data->stream, ++++++ snd_pcm_lib_buffer_bytes(substream)); ++++++ offset = bytes_to_frames(runtime, pos); ++++++ ++++++ dev_dbg(rtd->dev, "PCM: DMA pointer %zu bytes\n", ++++++ frames_to_bytes(runtime, (u32)offset)); ++++++ return offset; ++++++} ++++++ ++++++static int sst_byt_pcm_open(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct sst_byt_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct sst_byt_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_byt *byt = pdata->byt; ++++++ ++++++ dev_dbg(rtd->dev, "PCM: open\n"); ++++++ ++++++ pcm_data = &pdata->pcm[rtd->cpu_dai->id]; ++++++ mutex_lock(&pcm_data->mutex); ++++++ ++++++ snd_soc_pcm_set_drvdata(rtd, pcm_data); ++++++ pcm_data->substream = substream; ++++++ ++++++ snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware); ++++++ ++++++ pcm_data->stream = sst_byt_stream_new(byt, rtd->cpu_dai->id + 1, ++++++ byt_notify_pointer, pcm_data); ++++++ if (pcm_data->stream == NULL) { ++++++ dev_err(rtd->dev, "failed to create stream\n"); ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_pcm_close(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct sst_byt_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct sst_byt_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_byt *byt = pdata->byt; ++++++ int ret; ++++++ ++++++ dev_dbg(rtd->dev, "PCM: close\n"); ++++++ ++++++ mutex_lock(&pcm_data->mutex); ++++++ ret = sst_byt_stream_free(byt, pcm_data->stream); ++++++ if (ret < 0) { ++++++ dev_dbg(rtd->dev, "Free stream fail\n"); ++++++ goto out; ++++++ } ++++++ pcm_data->stream = NULL; ++++++ ++++++out: ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return ret; ++++++} ++++++ ++++++static int sst_byt_pcm_mmap(struct snd_pcm_substream *substream, ++++++ struct vm_area_struct *vma) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ ++++++ dev_dbg(rtd->dev, "PCM: mmap\n"); ++++++ return snd_pcm_lib_default_mmap(substream, vma); ++++++} ++++++ ++++++static struct snd_pcm_ops sst_byt_pcm_ops = { ++++++ .open = sst_byt_pcm_open, ++++++ .close = sst_byt_pcm_close, ++++++ .ioctl = snd_pcm_lib_ioctl, ++++++ .hw_params = sst_byt_pcm_hw_params, ++++++ .hw_free = sst_byt_pcm_hw_free, ++++++ .trigger = sst_byt_pcm_trigger, ++++++ .pointer = sst_byt_pcm_pointer, ++++++ .mmap = sst_byt_pcm_mmap, ++++++}; ++++++ ++++++static void sst_byt_pcm_free(struct snd_pcm *pcm) ++++++{ ++++++ snd_pcm_lib_preallocate_free_for_all(pcm); ++++++} ++++++ ++++++static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd) ++++++{ ++++++ struct snd_pcm *pcm = rtd->pcm; ++++++ size_t size; ++++++ int ret = 0; ++++++ ++++++ ret = dma_coerce_mask_and_coherent(rtd->card->dev, DMA_BIT_MASK(32)); ++++++ if (ret) ++++++ return ret; ++++++ ++++++ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || ++++++ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { ++++++ size = sst_byt_pcm_hardware.buffer_bytes_max; ++++++ ret = snd_pcm_lib_preallocate_pages_for_all(pcm, ++++++ SNDRV_DMA_TYPE_DEV, ++++++ rtd->card->dev, ++++++ size, size); ++++++ if (ret) { ++++++ dev_err(rtd->dev, "dma buffer allocation failed %d\n", ++++++ ret); ++++++ return ret; ++++++ } ++++++ } ++++++ ++++++ return ret; ++++++} ++++++ ++++++static struct snd_soc_dai_driver byt_dais[] = { ++++++ { ++++++ .name = "Front-cpu-dai", ++++++ .playback = { ++++++ .stream_name = "System Playback", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_48000, ++++++ .formats = SNDRV_PCM_FMTBIT_S24_3LE | ++++++ SNDRV_PCM_FMTBIT_S16_LE, ++++++ }, ++++++ }, ++++++ { ++++++ .name = "Mic1-cpu-dai", ++++++ .capture = { ++++++ .stream_name = "Analog Capture", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_48000, ++++++ .formats = SNDRV_PCM_FMTBIT_S16_LE, ++++++ }, ++++++ }, ++++++}; ++++++ ++++++static int sst_byt_pcm_probe(struct snd_soc_platform *platform) ++++++{ ++++++ struct sst_pdata *plat_data = dev_get_platdata(platform->dev); ++++++ struct sst_byt_priv_data *priv_data; ++++++ int i; ++++++ ++++++ if (!plat_data) ++++++ return -ENODEV; ++++++ ++++++ priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), ++++++ GFP_KERNEL); ++++++ priv_data->byt = plat_data->dsp; ++++++ snd_soc_platform_set_drvdata(platform, priv_data); ++++++ ++++++ for (i = 0; i < ARRAY_SIZE(byt_dais); i++) ++++++ mutex_init(&priv_data->pcm[i].mutex); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int sst_byt_pcm_remove(struct snd_soc_platform *platform) ++++++{ ++++++ return 0; ++++++} ++++++ ++++++static struct snd_soc_platform_driver byt_soc_platform = { ++++++ .probe = sst_byt_pcm_probe, ++++++ .remove = sst_byt_pcm_remove, ++++++ .ops = &sst_byt_pcm_ops, ++++++ .pcm_new = sst_byt_pcm_new, ++++++ .pcm_free = sst_byt_pcm_free, ++++++}; ++++++ ++++++static const struct snd_soc_component_driver byt_dai_component = { ++++++ .name = "byt-dai", ++++++}; ++++++ ++++++static int sst_byt_pcm_dev_probe(struct platform_device *pdev) ++++++{ ++++++ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); ++++++ int ret; ++++++ ++++++ ret = sst_byt_dsp_init(&pdev->dev, sst_pdata); ++++++ if (ret < 0) ++++++ return -ENODEV; ++++++ ++++++ ret = snd_soc_register_platform(&pdev->dev, &byt_soc_platform); ++++++ if (ret < 0) ++++++ goto err_plat; ++++++ ++++++ ret = snd_soc_register_component(&pdev->dev, &byt_dai_component, ++++++ byt_dais, ARRAY_SIZE(byt_dais)); ++++++ if (ret < 0) ++++++ goto err_comp; ++++++ ++++++ return 0; ++++++ ++++++err_comp: ++++++ snd_soc_unregister_platform(&pdev->dev); ++++++err_plat: ++++++ sst_byt_dsp_free(&pdev->dev, sst_pdata); ++++++ return ret; ++++++} ++++++ ++++++static int sst_byt_pcm_dev_remove(struct platform_device *pdev) ++++++{ ++++++ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); ++++++ ++++++ snd_soc_unregister_platform(&pdev->dev); ++++++ snd_soc_unregister_component(&pdev->dev); ++++++ sst_byt_dsp_free(&pdev->dev, sst_pdata); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct platform_driver sst_byt_pcm_driver = { ++++++ .driver = { ++++++ .name = "baytrail-pcm-audio", ++++++ .owner = THIS_MODULE, ++++++ }, ++++++ ++++++ .probe = sst_byt_pcm_dev_probe, ++++++ .remove = sst_byt_pcm_dev_remove, ++++++}; ++++++module_platform_driver(sst_byt_pcm_driver); ++++++ ++++++MODULE_AUTHOR("Jarkko Nikula"); ++++++MODULE_DESCRIPTION("Baytrail PCM"); ++++++MODULE_LICENSE("GPL v2"); ++++++MODULE_ALIAS("platform:baytrail-pcm-audio"); diff --cc sound/soc/intel/sst-dsp-priv.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..fe8e81aad646 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-dsp-priv.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,309 @@@@@@@ ++++++/* ++++++ * Intel Smart Sound Technology ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#ifndef __SOUND_SOC_SST_DSP_PRIV_H ++++++#define __SOUND_SOC_SST_DSP_PRIV_H ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++struct sst_mem_block; ++++++struct sst_module; ++++++struct sst_fw; ++++++ ++++++/* ++++++ * DSP Operations exported by platform Audio DSP driver. ++++++ */ ++++++struct sst_ops { ++++++ /* DSP core boot / reset */ ++++++ void (*boot)(struct sst_dsp *); ++++++ void (*reset)(struct sst_dsp *); ++++++ ++++++ /* Shim IO */ ++++++ void (*write)(void __iomem *addr, u32 offset, u32 value); ++++++ u32 (*read)(void __iomem *addr, u32 offset); ++++++ void (*write64)(void __iomem *addr, u32 offset, u64 value); ++++++ u64 (*read64)(void __iomem *addr, u32 offset); ++++++ ++++++ /* DSP I/DRAM IO */ ++++++ void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src, ++++++ size_t bytes); ++++++ void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src, ++++++ size_t bytes); ++++++ ++++++ void (*dump)(struct sst_dsp *); ++++++ ++++++ /* IRQ handlers */ ++++++ irqreturn_t (*irq_handler)(int irq, void *context); ++++++ ++++++ /* SST init and free */ ++++++ int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata); ++++++ void (*free)(struct sst_dsp *sst); ++++++ ++++++ /* FW module parser/loader */ ++++++ int (*parse_fw)(struct sst_fw *sst_fw); ++++++}; ++++++ ++++++/* ++++++ * Audio DSP memory offsets and addresses. ++++++ */ ++++++struct sst_addr { ++++++ u32 lpe_base; ++++++ u32 shim_offset; ++++++ u32 iram_offset; ++++++ u32 dram_offset; ++++++ void __iomem *lpe; ++++++ void __iomem *shim; ++++++ void __iomem *pci_cfg; ++++++ void __iomem *fw_ext; ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Mailbox configuration. ++++++ */ ++++++struct sst_mailbox { ++++++ void __iomem *in_base; ++++++ void __iomem *out_base; ++++++ size_t in_size; ++++++ size_t out_size; ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Firmware data types. ++++++ */ ++++++enum sst_data_type { ++++++ SST_DATA_M = 0, /* module block data */ ++++++ SST_DATA_P = 1, /* peristant data (text, data) */ ++++++ SST_DATA_S = 2, /* scratch data (usually buffers) */ ++++++}; ++++++ ++++++/* ++++++ * Audio DSP memory block types. ++++++ */ ++++++enum sst_mem_type { ++++++ SST_MEM_IRAM = 0, ++++++ SST_MEM_DRAM = 1, ++++++ SST_MEM_ANY = 2, ++++++ SST_MEM_CACHE= 3, ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Generic Firmware File. ++++++ * ++++++ * SST Firmware files can consist of 1..N modules. This generic structure is ++++++ * used to manage each firmware file and it's modules regardless of SST firmware ++++++ * type. A SST driver may load multiple FW files. ++++++ */ ++++++struct sst_fw { ++++++ struct sst_dsp *dsp; ++++++ ++++++ /* base addresses of FW file data */ ++++++ dma_addr_t dmable_fw_paddr; /* physical address of fw data */ ++++++ void *dma_buf; /* virtual address of fw data */ ++++++ u32 size; /* size of fw data */ ++++++ ++++++ /* lists */ ++++++ struct list_head list; /* DSP list of FW */ ++++++ struct list_head module_list; /* FW list of modules */ ++++++ ++++++ void *private; /* core doesn't touch this */ ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Generic Module data. ++++++ * ++++++ * This is used to dsecribe any sections of persistent (text and data) and ++++++ * scratch (buffers) of module data in ADSP memory space. ++++++ */ ++++++struct sst_module_data { ++++++ ++++++ enum sst_mem_type type; /* destination memory type */ ++++++ enum sst_data_type data_type; /* type of module data */ ++++++ ++++++ u32 size; /* size in bytes */ ++++++ u32 offset; /* offset in FW file */ ++++++ u32 data_offset; /* offset in ADSP memory space */ ++++++ void *data; /* module data */ ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Generic Module Template. ++++++ * ++++++ * Used to define and register a new FW module. This data is extracted from ++++++ * FW module header information. ++++++ */ ++++++struct sst_module_template { ++++++ u32 id; ++++++ u32 entry; /* entry point */ ++++++ struct sst_module_data s; /* scratch data */ ++++++ struct sst_module_data p; /* peristant data */ ++++++}; ++++++ ++++++/* ++++++ * Audio DSP Generic Module. ++++++ * ++++++ * Each Firmware file can consist of 1..N modules. A module can span multiple ++++++ * ADSP memory blocks. The simplest FW will be a file with 1 module. ++++++ */ ++++++struct sst_module { ++++++ struct sst_dsp *dsp; ++++++ struct sst_fw *sst_fw; /* parent FW we belong too */ ++++++ ++++++ /* module configuration */ ++++++ u32 id; ++++++ u32 entry; /* module entry point */ ++++++ u32 offset; /* module offset in firmware file */ ++++++ u32 size; /* module size */ ++++++ struct sst_module_data s; /* scratch data */ ++++++ struct sst_module_data p; /* peristant data */ ++++++ ++++++ /* runtime */ ++++++ u32 usage_count; /* can be unloaded if count == 0 */ ++++++ void *private; /* core doesn't touch this */ ++++++ ++++++ /* lists */ ++++++ struct list_head block_list; /* Module list of blocks in use */ ++++++ struct list_head list; /* DSP list of modules */ ++++++ struct list_head list_fw; /* FW list of modules */ ++++++}; ++++++ ++++++/* ++++++ * SST Memory Block operations. ++++++ */ ++++++struct sst_block_ops { ++++++ int (*enable)(struct sst_mem_block *block); ++++++ int (*disable)(struct sst_mem_block *block); ++++++}; ++++++ ++++++/* ++++++ * SST Generic Memory Block. ++++++ * ++++++ * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be ++++++ * power gated. ++++++ */ ++++++struct sst_mem_block { ++++++ struct sst_dsp *dsp; ++++++ struct sst_module *module; /* module that uses this block */ ++++++ ++++++ /* block config */ ++++++ u32 offset; /* offset from base */ ++++++ u32 size; /* block size */ ++++++ u32 index; /* block index 0..N */ ++++++ enum sst_mem_type type; /* block memory type IRAM/DRAM */ ++++++ struct sst_block_ops *ops; /* block operations, if any */ ++++++ ++++++ /* block status */ ++++++ enum sst_data_type data_type; /* data type held in this block */ ++++++ u32 bytes_used; /* bytes in use by modules */ ++++++ void *private; /* generic core does not touch this */ ++++++ int users; /* number of modules using this block */ ++++++ ++++++ /* block lists */ ++++++ struct list_head module_list; /* Module list of blocks */ ++++++ struct list_head list; /* Map list of free/used blocks */ ++++++}; ++++++ ++++++/* ++++++ * Generic SST Shim Interface. ++++++ */ ++++++struct sst_dsp { ++++++ ++++++ /* runtime */ ++++++ struct sst_dsp_device *sst_dev; ++++++ spinlock_t spinlock; /* IPC locking */ ++++++ struct mutex mutex; /* DSP FW lock */ ++++++ struct device *dev; ++++++ void *thread_context; ++++++ int irq; ++++++ u32 id; ++++++ ++++++ /* list of free and used ADSP memory blocks */ ++++++ struct list_head used_block_list; ++++++ struct list_head free_block_list; ++++++ ++++++ /* operations */ ++++++ struct sst_ops *ops; ++++++ ++++++ /* debug FS */ ++++++ struct dentry *debugfs_root; ++++++ ++++++ /* base addresses */ ++++++ struct sst_addr addr; ++++++ ++++++ /* mailbox */ ++++++ struct sst_mailbox mailbox; ++++++ ++++++ /* SST FW files loaded and their modules */ ++++++ struct list_head module_list; ++++++ struct list_head fw_list; ++++++ ++++++ /* platform data */ ++++++ struct sst_pdata *pdata; ++++++ ++++++ /* DMA FW loading */ ++++++ struct sst_dma *dma; ++++++ bool fw_use_dma; ++++++}; ++++++ ++++++/* Size optimised DRAM/IRAM memcpy */ ++++++static inline void sst_dsp_write(struct sst_dsp *sst, void *src, ++++++ u32 dest_offset, size_t bytes) ++++++{ ++++++ sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes); ++++++} ++++++ ++++++static inline void sst_dsp_read(struct sst_dsp *sst, void *dest, ++++++ u32 src_offset, size_t bytes) ++++++{ ++++++ sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes); ++++++} ++++++ ++++++static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst) ++++++{ ++++++ return sst->thread_context; ++++++} ++++++ ++++++/* Create/Free FW files - can contain multiple modules */ ++++++struct sst_fw *sst_fw_new(struct sst_dsp *dsp, ++++++ const struct firmware *fw, void *private); ++++++void sst_fw_free(struct sst_fw *sst_fw); ++++++void sst_fw_free_all(struct sst_dsp *dsp); ++++++ ++++++/* Create/Free firmware modules */ ++++++struct sst_module *sst_module_new(struct sst_fw *sst_fw, ++++++ struct sst_module_template *template, void *private); ++++++void sst_module_free(struct sst_module *sst_module); ++++++int sst_module_insert(struct sst_module *sst_module); ++++++int sst_module_remove(struct sst_module *sst_module); ++++++int sst_module_insert_fixed_block(struct sst_module *module, ++++++ struct sst_module_data *data); ++++++struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); ++++++ ++++++/* allocate/free pesistent/scratch memory regions managed by drv */ ++++++struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp); ++++++void sst_mem_block_free_scratch(struct sst_dsp *dsp, ++++++ struct sst_module *scratch); ++++++int sst_block_module_remove(struct sst_module *module); ++++++ ++++++/* Register the DSPs memory blocks - would be nice to read from ACPI */ ++++++struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, ++++++ u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, ++++++ void *private); ++++++void sst_mem_block_unregister_all(struct sst_dsp *dsp); ++++++ ++++++#endif diff --cc sound/soc/intel/sst-dsp.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..0c129fd85ecf new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-dsp.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,385 @@@@@@@ ++++++/* ++++++ * Intel Smart Sound Technology (SST) DSP Core Driver ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++ ++++++#define CREATE_TRACE_POINTS ++++++#include ++++++ ++++++/* Internal generic low-level SST IO functions - can be overidden */ ++++++void sst_shim32_write(void __iomem *addr, u32 offset, u32 value) ++++++{ ++++++ writel(value, addr + offset); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_shim32_write); ++++++ ++++++u32 sst_shim32_read(void __iomem *addr, u32 offset) ++++++{ ++++++ return readl(addr + offset); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_shim32_read); ++++++ ++++++void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) ++++++{ ++++++ memcpy_toio(addr + offset, &value, sizeof(value)); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_shim32_write64); ++++++ ++++++u64 sst_shim32_read64(void __iomem *addr, u32 offset) ++++++{ ++++++ u64 val; ++++++ ++++++ memcpy_fromio(&val, addr + offset, sizeof(val)); ++++++ return val; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_shim32_read64); ++++++ ++++++static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest, ++++++ u32 *src, size_t bytes) ++++++{ ++++++ int i, words = bytes >> 2; ++++++ ++++++ for (i = 0; i < words; i++) ++++++ writel(src[i], dest + i); ++++++} ++++++ ++++++static inline void _sst_memcpy_fromio_32(u32 *dest, ++++++ const volatile __iomem u32 *src, size_t bytes) ++++++{ ++++++ int i, words = bytes >> 2; ++++++ ++++++ for (i = 0; i < words; i++) ++++++ dest[i] = readl(src + i); ++++++} ++++++ ++++++void sst_memcpy_toio_32(struct sst_dsp *sst, ++++++ void __iomem *dest, void *src, size_t bytes) ++++++{ ++++++ _sst_memcpy_toio_32(dest, src, bytes); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_memcpy_toio_32); ++++++ ++++++void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest, ++++++ void __iomem *src, size_t bytes) ++++++{ ++++++ _sst_memcpy_fromio_32(dest, src, bytes); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32); ++++++ ++++++/* Public API */ ++++++void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value) ++++++{ ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ sst->ops->write(sst->addr.shim, offset, value); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_write); ++++++ ++++++u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) ++++++{ ++++++ unsigned long flags; ++++++ u32 val; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ val = sst->ops->read(sst->addr.shim, offset); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ ++++++ return val; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_read); ++++++ ++++++void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value) ++++++{ ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ sst->ops->write64(sst->addr.shim, offset, value); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_write64); ++++++ ++++++u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset) ++++++{ ++++++ unsigned long flags; ++++++ u64 val; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ val = sst->ops->read64(sst->addr.shim, offset); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ ++++++ return val; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_read64); ++++++ ++++++void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value) ++++++{ ++++++ sst->ops->write(sst->addr.shim, offset, value); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked); ++++++ ++++++u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) ++++++{ ++++++ return sst->ops->read(sst->addr.shim, offset); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked); ++++++ ++++++void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value) ++++++{ ++++++ sst->ops->write64(sst->addr.shim, offset, value); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked); ++++++ ++++++u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset) ++++++{ ++++++ return sst->ops->read64(sst->addr.shim, offset); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_unlocked); ++++++ ++++++int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, ++++++ u32 mask, u32 value) ++++++{ ++++++ bool change; ++++++ unsigned int old, new; ++++++ u32 ret; ++++++ ++++++ ret = sst_dsp_shim_read_unlocked(sst, offset); ++++++ ++++++ old = ret; ++++++ new = (old & (~mask)) | (value & mask); ++++++ ++++++ change = (old != new); ++++++ if (change) ++++++ sst_dsp_shim_write_unlocked(sst, offset, new); ++++++ ++++++ return change; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked); ++++++ ++++++int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, ++++++ u64 mask, u64 value) ++++++{ ++++++ bool change; ++++++ u64 old, new; ++++++ ++++++ old = sst_dsp_shim_read64_unlocked(sst, offset); ++++++ ++++++ new = (old & (~mask)) | (value & mask); ++++++ ++++++ change = (old != new); ++++++ if (change) ++++++ sst_dsp_shim_write64_unlocked(sst, offset, new); ++++++ ++++++ return change; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_unlocked); ++++++ ++++++int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, ++++++ u32 mask, u32 value) ++++++{ ++++++ unsigned long flags; ++++++ bool change; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ return change; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits); ++++++ ++++++int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, ++++++ u64 mask, u64 value) ++++++{ ++++++ unsigned long flags; ++++++ bool change; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value); ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ return change; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); ++++++ ++++++void sst_dsp_dump(struct sst_dsp *sst) ++++++{ ++++++ sst->ops->dump(sst); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_dump); ++++++ ++++++void sst_dsp_reset(struct sst_dsp *sst) ++++++{ ++++++ sst->ops->reset(sst); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_reset); ++++++ ++++++int sst_dsp_boot(struct sst_dsp *sst) ++++++{ ++++++ sst->ops->boot(sst); ++++++ return 0; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_boot); ++++++ ++++++void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) ++++++{ ++++++ sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); ++++++ trace_sst_ipc_msg_tx(msg); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx); ++++++ ++++++u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp) ++++++{ ++++++ u32 msg; ++++++ ++++++ msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); ++++++ trace_sst_ipc_msg_rx(msg); ++++++ ++++++ return msg; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx); ++++++ ++++++int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, ++++++ u32 outbox_offset, size_t outbox_size) ++++++{ ++++++ sst->mailbox.in_base = sst->addr.lpe + inbox_offset; ++++++ sst->mailbox.out_base = sst->addr.lpe + outbox_offset; ++++++ sst->mailbox.in_size = inbox_size; ++++++ sst->mailbox.out_size = outbox_size; ++++++ return 0; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init); ++++++ ++++++void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes) ++++++{ ++++++ u32 i; ++++++ ++++++ trace_sst_ipc_outbox_write(bytes); ++++++ ++++++ memcpy_toio(sst->mailbox.out_base, message, bytes); ++++++ ++++++ for (i = 0; i < bytes; i += 4) ++++++ trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i)); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_outbox_write); ++++++ ++++++void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes) ++++++{ ++++++ u32 i; ++++++ ++++++ trace_sst_ipc_outbox_read(bytes); ++++++ ++++++ memcpy_fromio(message, sst->mailbox.out_base, bytes); ++++++ ++++++ for (i = 0; i < bytes; i += 4) ++++++ trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i)); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_outbox_read); ++++++ ++++++void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes) ++++++{ ++++++ u32 i; ++++++ ++++++ trace_sst_ipc_inbox_write(bytes); ++++++ ++++++ memcpy_toio(sst->mailbox.in_base, message, bytes); ++++++ ++++++ for (i = 0; i < bytes; i += 4) ++++++ trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i)); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_inbox_write); ++++++ ++++++void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) ++++++{ ++++++ u32 i; ++++++ ++++++ trace_sst_ipc_inbox_read(bytes); ++++++ ++++++ memcpy_fromio(message, sst->mailbox.in_base, bytes); ++++++ ++++++ for (i = 0; i < bytes; i += 4) ++++++ trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i)); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); ++++++ ++++++struct sst_dsp *sst_dsp_new(struct device *dev, ++++++ struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) ++++++{ ++++++ struct sst_dsp *sst; ++++++ int err; ++++++ ++++++ dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); ++++++ ++++++ sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); ++++++ if (sst == NULL) ++++++ return NULL; ++++++ ++++++ spin_lock_init(&sst->spinlock); ++++++ mutex_init(&sst->mutex); ++++++ sst->dev = dev; ++++++ sst->thread_context = sst_dev->thread_context; ++++++ sst->sst_dev = sst_dev; ++++++ sst->id = pdata->id; ++++++ sst->irq = pdata->irq; ++++++ sst->ops = sst_dev->ops; ++++++ sst->pdata = pdata; ++++++ INIT_LIST_HEAD(&sst->used_block_list); ++++++ INIT_LIST_HEAD(&sst->free_block_list); ++++++ INIT_LIST_HEAD(&sst->module_list); ++++++ INIT_LIST_HEAD(&sst->fw_list); ++++++ ++++++ /* Initialise SST Audio DSP */ ++++++ if (sst->ops->init) { ++++++ err = sst->ops->init(sst, pdata); ++++++ if (err < 0) ++++++ return NULL; ++++++ } ++++++ ++++++ /* Register the ISR */ ++++++ err = request_threaded_irq(sst->irq, sst->ops->irq_handler, ++++++ sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); ++++++ if (err) ++++++ goto irq_err; ++++++ ++++++ return sst; ++++++ ++++++irq_err: ++++++ if (sst->ops->free) ++++++ sst->ops->free(sst); ++++++ ++++++ return NULL; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_new); ++++++ ++++++void sst_dsp_free(struct sst_dsp *sst) ++++++{ ++++++ free_irq(sst->irq, sst); ++++++ if (sst->ops->free) ++++++ sst->ops->free(sst); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_dsp_free); ++++++ ++++++/* Module information */ ++++++MODULE_AUTHOR("Liam Girdwood"); ++++++MODULE_DESCRIPTION("Intel SST Core"); ++++++MODULE_LICENSE("GPL v2"); diff --cc sound/soc/intel/sst-dsp.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..74052b59485c new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-dsp.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,233 @@@@@@@ ++++++/* ++++++ * Intel Smart Sound Technology (SST) Core ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#ifndef __SOUND_SOC_SST_DSP_H ++++++#define __SOUND_SOC_SST_DSP_H ++++++ ++++++#include ++++++#include ++++++#include ++++++ ++++++/* SST Device IDs */ ++++++#define SST_DEV_ID_LYNX_POINT 0x33C8 ++++++#define SST_DEV_ID_WILDCAT_POINT 0x3438 ++++++#define SST_DEV_ID_BYT 0x0F28 ++++++ ++++++/* Supported SST DMA Devices */ ++++++#define SST_DMA_TYPE_DW 1 ++++++#define SST_DMA_TYPE_MID 2 ++++++ ++++++/* SST Shim register map ++++++ * The register naming can differ between products. Some products also ++++++ * contain extra functionality. ++++++ */ ++++++#define SST_CSR 0x00 ++++++#define SST_PISR 0x08 ++++++#define SST_PIMR 0x10 ++++++#define SST_ISRX 0x18 ++++++#define SST_ISRD 0x20 ++++++#define SST_IMRX 0x28 ++++++#define SST_IMRD 0x30 ++++++#define SST_IPCX 0x38 /* IPC IA -> SST */ ++++++#define SST_IPCD 0x40 /* IPC SST -> IA */ ++++++#define SST_ISRSC 0x48 ++++++#define SST_ISRLPESC 0x50 ++++++#define SST_IMRSC 0x58 ++++++#define SST_IMRLPESC 0x60 ++++++#define SST_IPCSC 0x68 ++++++#define SST_IPCLPESC 0x70 ++++++#define SST_CLKCTL 0x78 ++++++#define SST_CSR2 0x80 ++++++#define SST_LTRC 0xE0 ++++++#define SST_HDMC 0xE8 ++++++#define SST_DBGO 0xF0 ++++++ ++++++#define SST_SHIM_SIZE 0x100 ++++++#define SST_PWMCTRL 0x1000 ++++++ ++++++/* SST Shim Register bits ++++++ * The register bit naming can differ between products. Some products also ++++++ * contain extra functionality. ++++++ */ ++++++ ++++++/* CSR / CS */ ++++++#define SST_CSR_RST (0x1 << 1) ++++++#define SST_CSR_SBCS0 (0x1 << 2) ++++++#define SST_CSR_SBCS1 (0x1 << 3) ++++++#define SST_CSR_DCS(x) (x << 4) ++++++#define SST_CSR_DCS_MASK (0x7 << 4) ++++++#define SST_CSR_STALL (0x1 << 10) ++++++#define SST_CSR_S0IOCS (0x1 << 21) ++++++#define SST_CSR_S1IOCS (0x1 << 23) ++++++#define SST_CSR_LPCS (0x1 << 31) ++++++#define SST_BYT_CSR_RST (0x1 << 0) ++++++#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) ++++++#define SST_BYT_CSR_STALL (0x1 << 2) ++++++#define SST_BYT_CSR_PWAITMODE (0x1 << 3) ++++++ ++++++/* ISRX / ISC */ ++++++#define SST_ISRX_BUSY (0x1 << 1) ++++++#define SST_ISRX_DONE (0x1 << 0) ++++++#define SST_BYT_ISRX_REQUEST (0x1 << 1) ++++++ ++++++/* ISRD / ISD */ ++++++#define SST_ISRD_BUSY (0x1 << 1) ++++++#define SST_ISRD_DONE (0x1 << 0) ++++++ ++++++/* IMRX / IMC */ ++++++#define SST_IMRX_BUSY (0x1 << 1) ++++++#define SST_IMRX_DONE (0x1 << 0) ++++++#define SST_BYT_IMRX_REQUEST (0x1 << 1) ++++++ ++++++/* IPCX / IPCC */ ++++++#define SST_IPCX_DONE (0x1 << 30) ++++++#define SST_IPCX_BUSY (0x1 << 31) ++++++#define SST_BYT_IPCX_DONE ((u64)0x1 << 62) ++++++#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63) ++++++ ++++++/* IPCD */ ++++++#define SST_IPCD_DONE (0x1 << 30) ++++++#define SST_IPCD_BUSY (0x1 << 31) ++++++#define SST_BYT_IPCD_DONE ((u64)0x1 << 62) ++++++#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63) ++++++ ++++++/* CLKCTL */ ++++++#define SST_CLKCTL_SMOS(x) (x << 24) ++++++#define SST_CLKCTL_MASK (3 << 24) ++++++#define SST_CLKCTL_DCPLCG (1 << 18) ++++++#define SST_CLKCTL_SCOE1 (1 << 17) ++++++#define SST_CLKCTL_SCOE0 (1 << 16) ++++++ ++++++/* CSR2 / CS2 */ ++++++#define SST_CSR2_SDFD_SSP0 (1 << 1) ++++++#define SST_CSR2_SDFD_SSP1 (1 << 2) ++++++ ++++++/* LTRC */ ++++++#define SST_LTRC_VAL(x) (x << 0) ++++++ ++++++/* HDMC */ ++++++#define SST_HDMC_HDDA0(x) (x << 0) ++++++#define SST_HDMC_HDDA1(x) (x << 7) ++++++ ++++++ ++++++/* SST Vendor Defined Registers and bits */ ++++++#define SST_VDRTCTL0 0xa0 ++++++#define SST_VDRTCTL1 0xa4 ++++++#define SST_VDRTCTL2 0xa8 ++++++#define SST_VDRTCTL3 0xaC ++++++ ++++++/* VDRTCTL0 */ ++++++#define SST_VDRTCL0_DSRAMPGE_SHIFT 16 ++++++#define SST_VDRTCL0_DSRAMPGE_MASK (0xffff << SST_VDRTCL0_DSRAMPGE_SHIFT) ++++++#define SST_VDRTCL0_ISRAMPGE_SHIFT 6 ++++++#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) ++++++ ++++++struct sst_dsp; ++++++ ++++++/* ++++++ * SST Device. ++++++ * ++++++ * This structure is populated by the SST core driver. ++++++ */ ++++++struct sst_dsp_device { ++++++ /* Mandatory fields */ ++++++ struct sst_ops *ops; ++++++ irqreturn_t (*thread)(int irq, void *context); ++++++ void *thread_context; ++++++}; ++++++ ++++++/* ++++++ * SST Platform Data. ++++++ */ ++++++struct sst_pdata { ++++++ /* ACPI data */ ++++++ u32 lpe_base; ++++++ u32 lpe_size; ++++++ u32 pcicfg_base; ++++++ u32 pcicfg_size; ++++++ u32 fw_base; ++++++ u32 fw_size; ++++++ int irq; ++++++ ++++++ /* Firmware */ ++++++ const struct firmware *fw; ++++++ ++++++ /* DMA */ ++++++ u32 dma_base; ++++++ u32 dma_size; ++++++ int dma_engine; ++++++ ++++++ /* DSP */ ++++++ u32 id; ++++++ void *dsp; ++++++}; ++++++ ++++++/* Initialization */ ++++++struct sst_dsp *sst_dsp_new(struct device *dev, ++++++ struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); ++++++void sst_dsp_free(struct sst_dsp *sst); ++++++ ++++++/* SHIM Read / Write */ ++++++void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value); ++++++u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset); ++++++int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, ++++++ u32 mask, u32 value); ++++++void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value); ++++++u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset); ++++++int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, ++++++ u64 mask, u64 value); ++++++ ++++++/* SHIM Read / Write Unlocked for callers already holding sst lock */ ++++++void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value); ++++++u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset); ++++++int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, ++++++ u32 mask, u32 value); ++++++void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value); ++++++u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset); ++++++int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, ++++++ u64 mask, u64 value); ++++++ ++++++/* Internal generic low-level SST IO functions - can be overidden */ ++++++void sst_shim32_write(void __iomem *addr, u32 offset, u32 value); ++++++u32 sst_shim32_read(void __iomem *addr, u32 offset); ++++++void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value); ++++++u64 sst_shim32_read64(void __iomem *addr, u32 offset); ++++++void sst_memcpy_toio_32(struct sst_dsp *sst, ++++++ void __iomem *dest, void *src, size_t bytes); ++++++void sst_memcpy_fromio_32(struct sst_dsp *sst, ++++++ void *dest, void __iomem *src, size_t bytes); ++++++ ++++++/* DSP reset & boot */ ++++++void sst_dsp_reset(struct sst_dsp *sst); ++++++int sst_dsp_boot(struct sst_dsp *sst); ++++++ ++++++/* Msg IO */ ++++++void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); ++++++u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp); ++++++ ++++++/* Mailbox management */ ++++++int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset, ++++++ size_t inbox_size, u32 outbox_offset, size_t outbox_size); ++++++void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes); ++++++void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes); ++++++void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes); ++++++void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes); ++++++void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes); ++++++ ++++++/* Debug */ ++++++void sst_dsp_dump(struct sst_dsp *sst); ++++++ ++++++#endif diff --cc sound/soc/intel/sst-firmware.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..f7687107cf7f new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-firmware.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,587 @@@@@@@ ++++++/* ++++++ * Intel SST Firmware Loader ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++ ++++++static void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) ++++++{ ++++++ u32 i; ++++++ ++++++ /* copy one 32 bit word at a time as 64 bit access is not supported */ ++++++ for (i = 0; i < bytes; i += 4) ++++++ memcpy_toio(dest + i, src + i, 4); ++++++} ++++++ ++++++/* create new generic firmware object */ ++++++struct sst_fw *sst_fw_new(struct sst_dsp *dsp, ++++++ const struct firmware *fw, void *private) ++++++{ ++++++ struct sst_fw *sst_fw; ++++++ int err; ++++++ ++++++ if (!dsp->ops->parse_fw) ++++++ return NULL; ++++++ ++++++ sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); ++++++ if (sst_fw == NULL) ++++++ return NULL; ++++++ ++++++ sst_fw->dsp = dsp; ++++++ sst_fw->private = private; ++++++ sst_fw->size = fw->size; ++++++ ++++++ err = dma_coerce_mask_and_coherent(dsp->dev, DMA_BIT_MASK(32)); ++++++ if (err < 0) { ++++++ kfree(sst_fw); ++++++ return NULL; ++++++ } ++++++ ++++++ /* allocate DMA buffer to store FW data */ ++++++ sst_fw->dma_buf = dma_alloc_coherent(dsp->dev, sst_fw->size, ++++++ &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL); ++++++ if (!sst_fw->dma_buf) { ++++++ dev_err(dsp->dev, "error: DMA alloc failed\n"); ++++++ kfree(sst_fw); ++++++ return NULL; ++++++ } ++++++ ++++++ /* copy FW data to DMA-able memory */ ++++++ memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); ++++++ ++++++ /* call core specific FW paser to load FW data into DSP */ ++++++ err = dsp->ops->parse_fw(sst_fw); ++++++ if (err < 0) { ++++++ dev_err(dsp->dev, "error: parse fw failed %d\n", err); ++++++ goto parse_err; ++++++ } ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_add(&sst_fw->list, &dsp->fw_list); ++++++ mutex_unlock(&dsp->mutex); ++++++ ++++++ return sst_fw; ++++++ ++++++parse_err: ++++++ dma_free_coherent(dsp->dev, sst_fw->size, ++++++ sst_fw->dma_buf, ++++++ sst_fw->dmable_fw_paddr); ++++++ kfree(sst_fw); ++++++ return NULL; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_fw_new); ++++++ ++++++/* free single firmware object */ ++++++void sst_fw_free(struct sst_fw *sst_fw) ++++++{ ++++++ struct sst_dsp *dsp = sst_fw->dsp; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_del(&sst_fw->list); ++++++ mutex_unlock(&dsp->mutex); ++++++ ++++++ dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, ++++++ sst_fw->dmable_fw_paddr); ++++++ kfree(sst_fw); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_fw_free); ++++++ ++++++/* free all firmware objects */ ++++++void sst_fw_free_all(struct sst_dsp *dsp) ++++++{ ++++++ struct sst_fw *sst_fw, *t; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { ++++++ ++++++ list_del(&sst_fw->list); ++++++ dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, ++++++ sst_fw->dmable_fw_paddr); ++++++ kfree(sst_fw); ++++++ } ++++++ mutex_unlock(&dsp->mutex); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_fw_free_all); ++++++ ++++++/* create a new SST generic module from FW template */ ++++++struct sst_module *sst_module_new(struct sst_fw *sst_fw, ++++++ struct sst_module_template *template, void *private) ++++++{ ++++++ struct sst_dsp *dsp = sst_fw->dsp; ++++++ struct sst_module *sst_module; ++++++ ++++++ sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); ++++++ if (sst_module == NULL) ++++++ return NULL; ++++++ ++++++ sst_module->id = template->id; ++++++ sst_module->dsp = dsp; ++++++ sst_module->sst_fw = sst_fw; ++++++ ++++++ memcpy(&sst_module->s, &template->s, sizeof(struct sst_module_data)); ++++++ memcpy(&sst_module->p, &template->p, sizeof(struct sst_module_data)); ++++++ ++++++ INIT_LIST_HEAD(&sst_module->block_list); ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_add(&sst_module->list, &dsp->module_list); ++++++ mutex_unlock(&dsp->mutex); ++++++ ++++++ return sst_module; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_module_new); ++++++ ++++++/* free firmware module and remove from available list */ ++++++void sst_module_free(struct sst_module *sst_module) ++++++{ ++++++ struct sst_dsp *dsp = sst_module->dsp; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_del(&sst_module->list); ++++++ mutex_unlock(&dsp->mutex); ++++++ ++++++ kfree(sst_module); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_module_free); ++++++ ++++++static struct sst_mem_block *find_block(struct sst_dsp *dsp, int type, ++++++ u32 offset) ++++++{ ++++++ struct sst_mem_block *block; ++++++ ++++++ list_for_each_entry(block, &dsp->free_block_list, list) { ++++++ if (block->type == type && block->offset == offset) ++++++ return block; ++++++ } ++++++ ++++++ return NULL; ++++++} ++++++ ++++++static int block_alloc_contiguous(struct sst_module *module, ++++++ struct sst_module_data *data, u32 offset, int size) ++++++{ ++++++ struct list_head tmp = LIST_HEAD_INIT(tmp); ++++++ struct sst_dsp *dsp = module->dsp; ++++++ struct sst_mem_block *block; ++++++ ++++++ while (size > 0) { ++++++ block = find_block(dsp, data->type, offset); ++++++ if (!block) { ++++++ list_splice(&tmp, &dsp->free_block_list); ++++++ return -ENOMEM; ++++++ } ++++++ ++++++ list_move_tail(&block->list, &tmp); ++++++ offset += block->size; ++++++ size -= block->size; ++++++ } ++++++ ++++++ list_splice(&tmp, &dsp->used_block_list); ++++++ return 0; ++++++} ++++++ ++++++/* allocate free DSP blocks for module data - callers hold locks */ ++++++static int block_alloc(struct sst_module *module, ++++++ struct sst_module_data *data) ++++++{ ++++++ struct sst_dsp *dsp = module->dsp; ++++++ struct sst_mem_block *block, *tmp; ++++++ int ret = 0; ++++++ ++++++ if (data->size == 0) ++++++ return 0; ++++++ ++++++ /* find first free whole blocks that can hold module */ ++++++ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { ++++++ ++++++ /* ignore blocks with wrong type */ ++++++ if (block->type != data->type) ++++++ continue; ++++++ ++++++ if (data->size > block->size) ++++++ continue; ++++++ ++++++ data->offset = block->offset; ++++++ block->data_type = data->data_type; ++++++ block->bytes_used = data->size % block->size; ++++++ list_add(&block->module_list, &module->block_list); ++++++ list_move(&block->list, &dsp->used_block_list); ++++++ dev_dbg(dsp->dev, " *module %d added block %d:%d\n", ++++++ module->id, block->type, block->index); ++++++ return 0; ++++++ } ++++++ ++++++ /* then find free multiple blocks that can hold module */ ++++++ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { ++++++ ++++++ /* ignore blocks with wrong type */ ++++++ if (block->type != data->type) ++++++ continue; ++++++ ++++++ /* do we span > 1 blocks */ ++++++ if (data->size > block->size) { ++++++ ret = block_alloc_contiguous(module, data, ++++++ block->offset + block->size, ++++++ data->size - block->size); ++++++ if (ret == 0) ++++++ return ret; ++++++ } ++++++ } ++++++ ++++++ /* not enough free block space */ ++++++ return -ENOMEM; ++++++} ++++++ ++++++/* remove module from memory - callers hold locks */ ++++++static void block_module_remove(struct sst_module *module) ++++++{ ++++++ struct sst_mem_block *block, *tmp; ++++++ struct sst_dsp *dsp = module->dsp; ++++++ int err; ++++++ ++++++ /* disable each block */ ++++++ list_for_each_entry(block, &module->block_list, module_list) { ++++++ ++++++ if (block->ops && block->ops->disable) { ++++++ err = block->ops->disable(block); ++++++ if (err < 0) ++++++ dev_err(dsp->dev, ++++++ "error: cant disable block %d:%d\n", ++++++ block->type, block->index); ++++++ } ++++++ } ++++++ ++++++ /* mark each block as free */ ++++++ list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { ++++++ list_del(&block->module_list); ++++++ list_move(&block->list, &dsp->free_block_list); ++++++ } ++++++} ++++++ ++++++/* prepare the memory block to receive data from host - callers hold locks */ ++++++static int block_module_prepare(struct sst_module *module) ++++++{ ++++++ struct sst_mem_block *block; ++++++ int ret = 0; ++++++ ++++++ /* enable each block so that's it'e ready for module P/S data */ ++++++ list_for_each_entry(block, &module->block_list, module_list) { ++++++ ++++++ if (block->ops && block->ops->enable) { ++++++ ret = block->ops->enable(block); ++++++ if (ret < 0) { ++++++ dev_err(module->dsp->dev, ++++++ "error: cant disable block %d:%d\n", ++++++ block->type, block->index); ++++++ goto err; ++++++ } ++++++ } ++++++ } ++++++ return ret; ++++++ ++++++err: ++++++ list_for_each_entry(block, &module->block_list, module_list) { ++++++ if (block->ops && block->ops->disable) ++++++ block->ops->disable(block); ++++++ } ++++++ return ret; ++++++} ++++++ ++++++/* allocate memory blocks for static module addresses - callers hold locks */ ++++++static int block_alloc_fixed(struct sst_module *module, ++++++ struct sst_module_data *data) ++++++{ ++++++ struct sst_dsp *dsp = module->dsp; ++++++ struct sst_mem_block *block, *tmp; ++++++ u32 end = data->offset + data->size, block_end; ++++++ int err; ++++++ ++++++ /* only IRAM/DRAM blocks are managed */ ++++++ if (data->type != SST_MEM_IRAM && data->type != SST_MEM_DRAM) ++++++ return 0; ++++++ ++++++ /* are blocks already attached to this module */ ++++++ list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { ++++++ ++++++ /* force compacting mem blocks of the same data_type */ ++++++ if (block->data_type != data->data_type) ++++++ continue; ++++++ ++++++ block_end = block->offset + block->size; ++++++ ++++++ /* find block that holds section */ ++++++ if (data->offset >= block->offset && end < block_end) ++++++ return 0; ++++++ ++++++ /* does block span more than 1 section */ ++++++ if (data->offset >= block->offset && data->offset < block_end) { ++++++ ++++++ err = block_alloc_contiguous(module, data, ++++++ block->offset + block->size, ++++++ data->size - block->size + data->offset - block->offset); ++++++ if (err < 0) ++++++ return -ENOMEM; ++++++ ++++++ /* module already owns blocks */ ++++++ return 0; ++++++ } ++++++ } ++++++ ++++++ /* find first free blocks that can hold section in free list */ ++++++ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { ++++++ block_end = block->offset + block->size; ++++++ ++++++ /* find block that holds section */ ++++++ if (data->offset >= block->offset && end < block_end) { ++++++ ++++++ /* add block */ ++++++ block->data_type = data->data_type; ++++++ list_move(&block->list, &dsp->used_block_list); ++++++ list_add(&block->module_list, &module->block_list); ++++++ return 0; ++++++ } ++++++ ++++++ /* does block span more than 1 section */ ++++++ if (data->offset >= block->offset && data->offset < block_end) { ++++++ ++++++ err = block_alloc_contiguous(module, data, ++++++ block->offset + block->size, ++++++ data->size - block->size); ++++++ if (err < 0) ++++++ return -ENOMEM; ++++++ ++++++ /* add block */ ++++++ block->data_type = data->data_type; ++++++ list_move(&block->list, &dsp->used_block_list); ++++++ list_add(&block->module_list, &module->block_list); ++++++ return 0; ++++++ } ++++++ ++++++ } ++++++ ++++++ return -ENOMEM; ++++++} ++++++ ++++++/* Load fixed module data into DSP memory blocks */ ++++++int sst_module_insert_fixed_block(struct sst_module *module, ++++++ struct sst_module_data *data) ++++++{ ++++++ struct sst_dsp *dsp = module->dsp; ++++++ int ret; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ ++++++ /* alloc blocks that includes this section */ ++++++ ret = block_alloc_fixed(module, data); ++++++ if (ret < 0) { ++++++ dev_err(dsp->dev, ++++++ "error: no free blocks for section at offset 0x%x size 0x%x\n", ++++++ data->offset, data->size); ++++++ mutex_unlock(&dsp->mutex); ++++++ return -ENOMEM; ++++++ } ++++++ ++++++ /* prepare DSP blocks for module copy */ ++++++ ret = block_module_prepare(module); ++++++ if (ret < 0) { ++++++ dev_err(dsp->dev, "error: fw module prepare failed\n"); ++++++ goto err; ++++++ } ++++++ ++++++ /* copy partial module data to blocks */ ++++++ sst_memcpy32(dsp->addr.lpe + data->offset, data->data, data->size); ++++++ ++++++ mutex_unlock(&dsp->mutex); ++++++ return ret; ++++++ ++++++err: ++++++ block_module_remove(module); ++++++ mutex_unlock(&dsp->mutex); ++++++ return ret; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_module_insert_fixed_block); ++++++ ++++++/* Unload entire module from DSP memory */ ++++++int sst_block_module_remove(struct sst_module *module) ++++++{ ++++++ struct sst_dsp *dsp = module->dsp; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ block_module_remove(module); ++++++ mutex_unlock(&dsp->mutex); ++++++ return 0; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_block_module_remove); ++++++ ++++++/* register a DSP memory block for use with FW based modules */ ++++++struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, ++++++ u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index, ++++++ void *private) ++++++{ ++++++ struct sst_mem_block *block; ++++++ ++++++ block = kzalloc(sizeof(*block), GFP_KERNEL); ++++++ if (block == NULL) ++++++ return NULL; ++++++ ++++++ block->offset = offset; ++++++ block->size = size; ++++++ block->index = index; ++++++ block->type = type; ++++++ block->dsp = dsp; ++++++ block->private = private; ++++++ block->ops = ops; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ list_add(&block->list, &dsp->free_block_list); ++++++ mutex_unlock(&dsp->mutex); ++++++ ++++++ return block; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_mem_block_register); ++++++ ++++++/* unregister all DSP memory blocks */ ++++++void sst_mem_block_unregister_all(struct sst_dsp *dsp) ++++++{ ++++++ struct sst_mem_block *block, *tmp; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ ++++++ /* unregister used blocks */ ++++++ list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { ++++++ list_del(&block->list); ++++++ kfree(block); ++++++ } ++++++ ++++++ /* unregister free blocks */ ++++++ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { ++++++ list_del(&block->list); ++++++ kfree(block); ++++++ } ++++++ ++++++ mutex_unlock(&dsp->mutex); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); ++++++ ++++++/* allocate scratch buffer blocks */ ++++++struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp) ++++++{ ++++++ struct sst_module *sst_module, *scratch; ++++++ struct sst_mem_block *block, *tmp; ++++++ u32 block_size; ++++++ int ret = 0; ++++++ ++++++ scratch = kzalloc(sizeof(struct sst_module), GFP_KERNEL); ++++++ if (scratch == NULL) ++++++ return NULL; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ ++++++ /* calculate required scratch size */ ++++++ list_for_each_entry(sst_module, &dsp->module_list, list) { ++++++ if (scratch->s.size > sst_module->s.size) ++++++ scratch->s.size = scratch->s.size; ++++++ else ++++++ scratch->s.size = sst_module->s.size; ++++++ } ++++++ ++++++ dev_dbg(dsp->dev, "scratch buffer required is %d bytes\n", ++++++ scratch->s.size); ++++++ ++++++ /* init scratch module */ ++++++ scratch->dsp = dsp; ++++++ scratch->s.type = SST_MEM_DRAM; ++++++ scratch->s.data_type = SST_DATA_S; ++++++ INIT_LIST_HEAD(&scratch->block_list); ++++++ ++++++ /* check free blocks before looking at used blocks for space */ ++++++ if (!list_empty(&dsp->free_block_list)) ++++++ block = list_first_entry(&dsp->free_block_list, ++++++ struct sst_mem_block, list); ++++++ else ++++++ block = list_first_entry(&dsp->used_block_list, ++++++ struct sst_mem_block, list); ++++++ block_size = block->size; ++++++ ++++++ /* allocate blocks for module scratch buffers */ ++++++ dev_dbg(dsp->dev, "allocating scratch blocks\n"); ++++++ ret = block_alloc(scratch, &scratch->s); ++++++ if (ret < 0) { ++++++ dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); ++++++ goto err; ++++++ } ++++++ ++++++ /* assign the same offset of scratch to each module */ ++++++ list_for_each_entry(sst_module, &dsp->module_list, list) ++++++ sst_module->s.offset = scratch->s.offset; ++++++ ++++++ mutex_unlock(&dsp->mutex); ++++++ return scratch; ++++++ ++++++err: ++++++ list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) ++++++ list_del(&block->module_list); ++++++ mutex_unlock(&dsp->mutex); ++++++ return NULL; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_mem_block_alloc_scratch); ++++++ ++++++/* free all scratch blocks */ ++++++void sst_mem_block_free_scratch(struct sst_dsp *dsp, ++++++ struct sst_module *scratch) ++++++{ ++++++ struct sst_mem_block *block, *tmp; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ ++++++ list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) ++++++ list_del(&block->module_list); ++++++ ++++++ mutex_unlock(&dsp->mutex); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_mem_block_free_scratch); ++++++ ++++++/* get a module from it's unique ID */ ++++++struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) ++++++{ ++++++ struct sst_module *module; ++++++ ++++++ mutex_lock(&dsp->mutex); ++++++ ++++++ list_for_each_entry(module, &dsp->module_list, list) { ++++++ if (module->id == id) { ++++++ mutex_unlock(&dsp->mutex); ++++++ return module; ++++++ } ++++++ } ++++++ ++++++ mutex_unlock(&dsp->mutex); ++++++ return NULL; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_module_get_from_id); diff --cc sound/soc/intel/sst-haswell-dsp.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..f5ebf36af889 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-haswell-dsp.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,517 @@@@@@@ ++++++/* ++++++ * Intel Haswell SST DSP driver ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include ++++++#include ++++++ ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++#include "sst-haswell-ipc.h" ++++++ ++++++#include ++++++ ++++++#define SST_HSW_FW_SIGNATURE_SIZE 4 ++++++#define SST_HSW_FW_SIGN "$SST" ++++++#define SST_HSW_FW_LIB_SIGN "$LIB" ++++++ ++++++#define SST_WPT_SHIM_OFFSET 0xFB000 ++++++#define SST_LP_SHIM_OFFSET 0xE7000 ++++++#define SST_WPT_IRAM_OFFSET 0xA0000 ++++++#define SST_LP_IRAM_OFFSET 0x80000 ++++++ ++++++#define SST_SHIM_PM_REG 0x84 ++++++ ++++++#define SST_HSW_IRAM 1 ++++++#define SST_HSW_DRAM 2 ++++++#define SST_HSW_REGS 3 ++++++ ++++++struct dma_block_info { ++++++ __le32 type; /* IRAM/DRAM */ ++++++ __le32 size; /* Bytes */ ++++++ __le32 ram_offset; /* Offset in I/DRAM */ ++++++ __le32 rsvd; /* Reserved field */ ++++++} __attribute__((packed)); ++++++ ++++++struct fw_module_info { ++++++ __le32 persistent_size; ++++++ __le32 scratch_size; ++++++} __attribute__((packed)); ++++++ ++++++struct fw_header { ++++++ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */ ++++++ __le32 file_size; /* size of fw minus this header */ ++++++ __le32 modules; /* # of modules */ ++++++ __le32 file_format; /* version of header format */ ++++++ __le32 reserved[4]; ++++++} __attribute__((packed)); ++++++ ++++++struct fw_module_header { ++++++ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */ ++++++ __le32 mod_size; /* size of module */ ++++++ __le32 blocks; /* # of blocks */ ++++++ __le16 padding; ++++++ __le16 type; /* codec type, pp lib */ ++++++ __le32 entry_point; ++++++ struct fw_module_info info; ++++++} __attribute__((packed)); ++++++ ++++++static void hsw_free(struct sst_dsp *sst); ++++++ ++++++static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, ++++++ struct fw_module_header *module) ++++++{ ++++++ struct dma_block_info *block; ++++++ struct sst_module *mod; ++++++ struct sst_module_data block_data; ++++++ struct sst_module_template template; ++++++ int count; ++++++ void __iomem *ram; ++++++ ++++++ /* TODO: allowed module types need to be configurable */ ++++++ if (module->type != SST_HSW_MODULE_BASE_FW ++++++ && module->type != SST_HSW_MODULE_PCM_SYSTEM ++++++ && module->type != SST_HSW_MODULE_PCM ++++++ && module->type != SST_HSW_MODULE_PCM_REFERENCE ++++++ && module->type != SST_HSW_MODULE_PCM_CAPTURE ++++++ && module->type != SST_HSW_MODULE_LPAL) ++++++ return 0; ++++++ ++++++ dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", ++++++ module->signature, module->mod_size, ++++++ module->blocks, module->type); ++++++ dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->entry_point); ++++++ dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n", ++++++ module->info.persistent_size, module->info.scratch_size); ++++++ ++++++ memset(&template, 0, sizeof(template)); ++++++ template.id = module->type; ++++++ template.entry = module->entry_point; ++++++ template.p.size = module->info.persistent_size; ++++++ template.p.type = SST_MEM_DRAM; ++++++ template.p.data_type = SST_DATA_P; ++++++ template.s.size = module->info.scratch_size; ++++++ template.s.type = SST_MEM_DRAM; ++++++ template.s.data_type = SST_DATA_S; ++++++ ++++++ mod = sst_module_new(fw, &template, NULL); ++++++ if (mod == NULL) ++++++ return -ENOMEM; ++++++ ++++++ block = (void *)module + sizeof(*module); ++++++ ++++++ for (count = 0; count < module->blocks; count++) { ++++++ ++++++ if (block->size <= 0) { ++++++ dev_err(dsp->dev, ++++++ "error: block %d size invalid\n", count); ++++++ sst_module_free(mod); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ switch (block->type) { ++++++ case SST_HSW_IRAM: ++++++ ram = dsp->addr.lpe; ++++++ block_data.offset = ++++++ block->ram_offset + dsp->addr.iram_offset; ++++++ block_data.type = SST_MEM_IRAM; ++++++ break; ++++++ case SST_HSW_DRAM: ++++++ ram = dsp->addr.lpe; ++++++ block_data.offset = block->ram_offset; ++++++ block_data.type = SST_MEM_DRAM; ++++++ break; ++++++ default: ++++++ dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", ++++++ block->type, count); ++++++ sst_module_free(mod); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ block_data.size = block->size; ++++++ block_data.data_type = SST_DATA_M; ++++++ block_data.data = (void *)block + sizeof(*block); ++++++ block_data.data_offset = block_data.data - fw->dma_buf; ++++++ ++++++ dev_dbg(dsp->dev, "copy firmware block %d type 0x%x " ++++++ "size 0x%x ==> ram %p offset 0x%x\n", ++++++ count, block->type, block->size, ram, ++++++ block->ram_offset); ++++++ ++++++ sst_module_insert_fixed_block(mod, &block_data); ++++++ ++++++ block = (void *)block + sizeof(*block) + block->size; ++++++ } ++++++ return 0; ++++++} ++++++ ++++++static int hsw_parse_fw_image(struct sst_fw *sst_fw) ++++++{ ++++++ struct fw_header *header; ++++++ struct sst_module *scratch; ++++++ struct fw_module_header *module; ++++++ struct sst_dsp *dsp = sst_fw->dsp; ++++++ struct sst_hsw *hsw = sst_fw->private; ++++++ int ret, count; ++++++ ++++++ /* Read the header information from the data pointer */ ++++++ header = (struct fw_header *)sst_fw->dma_buf; ++++++ ++++++ /* verify FW */ ++++++ if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || ++++++ (sst_fw->size != header->file_size + sizeof(*header))) { ++++++ dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", ++++++ header->file_size, header->modules, ++++++ header->file_format, sizeof(*header)); ++++++ ++++++ /* parse each module */ ++++++ module = (void *)sst_fw->dma_buf + sizeof(*header); ++++++ for (count = 0; count < header->modules; count++) { ++++++ ++++++ /* module */ ++++++ ret = hsw_parse_module(dsp, sst_fw, module); ++++++ if (ret < 0) { ++++++ dev_err(dsp->dev, "error: invalid module %d\n", count); ++++++ return ret; ++++++ } ++++++ module = (void *)module + sizeof(*module) + module->mod_size; ++++++ } ++++++ ++++++ /* allocate persistent/scratch mem regions */ ++++++ scratch = sst_mem_block_alloc_scratch(dsp); ++++++ if (scratch == NULL) ++++++ return -ENOMEM; ++++++ ++++++ sst_hsw_set_scratch_module(hsw, scratch); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static irqreturn_t hsw_irq(int irq, void *context) ++++++{ ++++++ struct sst_dsp *sst = (struct sst_dsp *) context; ++++++ u32 isr; ++++++ int ret = IRQ_NONE; ++++++ ++++++ spin_lock(&sst->spinlock); ++++++ ++++++ /* Interrupt arrived, check src */ ++++++ isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); ++++++ if (isr & SST_ISRX_DONE) { ++++++ trace_sst_irq_done(isr, ++++++ sst_dsp_shim_read_unlocked(sst, SST_IMRX)); ++++++ ++++++ /* Mask Done interrupt before return */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, ++++++ SST_IMRX_DONE, SST_IMRX_DONE); ++++++ ret = IRQ_WAKE_THREAD; ++++++ } ++++++ ++++++ if (isr & SST_ISRX_BUSY) { ++++++ trace_sst_irq_busy(isr, ++++++ sst_dsp_shim_read_unlocked(sst, SST_IMRX)); ++++++ ++++++ /* Mask Busy interrupt before return */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, ++++++ SST_IMRX_BUSY, SST_IMRX_BUSY); ++++++ ret = IRQ_WAKE_THREAD; ++++++ } ++++++ ++++++ spin_unlock(&sst->spinlock); ++++++ return ret; ++++++} ++++++ ++++++static void hsw_boot(struct sst_dsp *sst) ++++++{ ++++++ /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, ++++++ SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); ++++++ ++++++ /* stall DSP core, set clk to 192/96Mhz */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, ++++++ SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK, ++++++ SST_CSR_STALL | SST_CSR_DCS(4)); ++++++ ++++++ /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, ++++++ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, ++++++ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); ++++++ ++++++ /* disable DMA finish function for SSP0 & SSP1 */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, ++++++ SST_CSR2_SDFD_SSP1); ++++++ ++++++ /* enable DMA engine 0,1 all channels to access host memory */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_HDMC, ++++++ SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff), ++++++ SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff)); ++++++ ++++++ /* disable all clock gating */ ++++++ writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); ++++++ ++++++ /* set DSP to RUN */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); ++++++} ++++++ ++++++static void hsw_reset(struct sst_dsp *sst) ++++++{ ++++++ /* put DSP into reset and stall */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, ++++++ SST_CSR_RST | SST_CSR_STALL, SST_CSR_RST | SST_CSR_STALL); ++++++ ++++++ /* keep in reset for 10ms */ ++++++ mdelay(10); ++++++ ++++++ /* take DSP out of reset and keep stalled for FW loading */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, ++++++ SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); ++++++} ++++++ ++++++struct sst_adsp_memregion { ++++++ u32 start; ++++++ u32 end; ++++++ int blocks; ++++++ enum sst_mem_type type; ++++++}; ++++++ ++++++/* lynx point ADSP mem regions */ ++++++static const struct sst_adsp_memregion lp_region[] = { ++++++ {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ ++++++ {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ ++++++ {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */ ++++++}; ++++++ ++++++/* wild cat point ADSP mem regions */ ++++++static const struct sst_adsp_memregion wpt_region[] = { ++++++ {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ ++++++ {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ ++++++ {0x80000, 0xA0000, 4, SST_MEM_DRAM}, /* D-SRAM2 - 4 * 32kB */ ++++++ {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ ++++++}; ++++++ ++++++static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) ++++++{ ++++++ /* ADSP DRAM & IRAM */ ++++++ sst->addr.lpe_base = pdata->lpe_base; ++++++ sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); ++++++ if (!sst->addr.lpe) ++++++ return -ENODEV; ++++++ ++++++ /* ADSP PCI MMIO config space */ ++++++ sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); ++++++ if (!sst->addr.pci_cfg) { ++++++ iounmap(sst->addr.lpe); ++++++ return -ENODEV; ++++++ } ++++++ ++++++ /* SST Shim */ ++++++ sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; ++++++ return 0; ++++++} ++++++ ++++++static u32 hsw_block_get_bit(struct sst_mem_block *block) ++++++{ ++++++ u32 bit = 0, shift = 0; ++++++ ++++++ switch (block->type) { ++++++ case SST_MEM_DRAM: ++++++ shift = 16; ++++++ break; ++++++ case SST_MEM_IRAM: ++++++ shift = 6; ++++++ break; ++++++ default: ++++++ return 0; ++++++ } ++++++ ++++++ bit = 1 << (block->index + shift); ++++++ ++++++ return bit; ++++++} ++++++ ++++++/* enable 32kB memory block - locks held by caller */ ++++++static int hsw_block_enable(struct sst_mem_block *block) ++++++{ ++++++ struct sst_dsp *sst = block->dsp; ++++++ u32 bit, val; ++++++ ++++++ if (block->users++ > 0) ++++++ return 0; ++++++ ++++++ dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", ++++++ block->type, block->index, block->offset); ++++++ ++++++ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); ++++++ bit = hsw_block_get_bit(block); ++++++ writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); ++++++ ++++++ /* wait 18 DSP clock ticks */ ++++++ udelay(10); ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* disable 32kB memory block - locks held by caller */ ++++++static int hsw_block_disable(struct sst_mem_block *block) ++++++{ ++++++ struct sst_dsp *sst = block->dsp; ++++++ u32 bit, val; ++++++ ++++++ if (--block->users > 0) ++++++ return 0; ++++++ ++++++ dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", ++++++ block->type, block->index, block->offset); ++++++ ++++++ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); ++++++ bit = hsw_block_get_bit(block); ++++++ writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct sst_block_ops sst_hsw_ops = { ++++++ .enable = hsw_block_enable, ++++++ .disable = hsw_block_disable, ++++++}; ++++++ ++++++static int hsw_enable_shim(struct sst_dsp *sst) ++++++{ ++++++ int tries = 10; ++++++ u32 reg; ++++++ ++++++ /* enable shim */ ++++++ reg = readl(sst->addr.pci_cfg + SST_SHIM_PM_REG); ++++++ writel(reg & ~0x3, sst->addr.pci_cfg + SST_SHIM_PM_REG); ++++++ ++++++ /* check that ADSP shim is enabled */ ++++++ while (tries--) { ++++++ reg = sst_dsp_shim_read_unlocked(sst, SST_CSR); ++++++ if (reg != 0xffffffff) ++++++ return 0; ++++++ ++++++ msleep(1); ++++++ } ++++++ ++++++ return -ENODEV; ++++++} ++++++ ++++++static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) ++++++{ ++++++ const struct sst_adsp_memregion *region; ++++++ struct device *dev; ++++++ int ret = -ENODEV, i, j, region_count; ++++++ u32 offset, size; ++++++ ++++++ dev = sst->dev; ++++++ ++++++ switch (sst->id) { ++++++ case SST_DEV_ID_LYNX_POINT: ++++++ region = lp_region; ++++++ region_count = ARRAY_SIZE(lp_region); ++++++ sst->addr.iram_offset = SST_LP_IRAM_OFFSET; ++++++ sst->addr.shim_offset = SST_LP_SHIM_OFFSET; ++++++ break; ++++++ case SST_DEV_ID_WILDCAT_POINT: ++++++ region = wpt_region; ++++++ region_count = ARRAY_SIZE(wpt_region); ++++++ sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; ++++++ sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; ++++++ break; ++++++ default: ++++++ dev_err(dev, "error: failed to get mem resources\n"); ++++++ return ret; ++++++ } ++++++ ++++++ ret = hsw_acpi_resource_map(sst, pdata); ++++++ if (ret < 0) { ++++++ dev_err(dev, "error: failed to map resources\n"); ++++++ return ret; ++++++ } ++++++ ++++++ /* enable the DSP SHIM */ ++++++ ret = hsw_enable_shim(sst); ++++++ if (ret < 0) { ++++++ dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); ++++++ return ret; ++++++ } ++++++ ++++++ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); ++++++ if (ret) ++++++ return ret; ++++++ ++++++ /* Enable Interrupt from both sides */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, 0x3, 0x0); ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRD, ++++++ (0x3 | 0x1 << 16 | 0x3 << 21), 0x0); ++++++ ++++++ /* register DSP memory blocks - ideally we should get this from ACPI */ ++++++ for (i = 0; i < region_count; i++) { ++++++ offset = region[i].start; ++++++ size = (region[i].end - region[i].start) / region[i].blocks; ++++++ ++++++ /* register individual memory blocks */ ++++++ for (j = 0; j < region[i].blocks; j++) { ++++++ sst_mem_block_register(sst, offset, size, ++++++ region[i].type, &sst_hsw_ops, j, sst); ++++++ offset += size; ++++++ } ++++++ } ++++++ ++++++ /* set default power gating mask */ ++++++ writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL0); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static void hsw_free(struct sst_dsp *sst) ++++++{ ++++++ sst_mem_block_unregister_all(sst); ++++++ iounmap(sst->addr.lpe); ++++++ iounmap(sst->addr.pci_cfg); ++++++} ++++++ ++++++struct sst_ops haswell_ops = { ++++++ .reset = hsw_reset, ++++++ .boot = hsw_boot, ++++++ .write = sst_shim32_write, ++++++ .read = sst_shim32_read, ++++++ .write64 = sst_shim32_write64, ++++++ .read64 = sst_shim32_read64, ++++++ .ram_read = sst_memcpy_fromio_32, ++++++ .ram_write = sst_memcpy_toio_32, ++++++ .irq_handler = hsw_irq, ++++++ .init = hsw_init, ++++++ .free = hsw_free, ++++++ .parse_fw = hsw_parse_fw_image, ++++++}; diff --cc sound/soc/intel/sst-haswell-ipc.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..f46bb4ddde6f new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-haswell-ipc.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,1785 @@@@@@@ ++++++/* ++++++ * Intel SST Haswell/Broadwell IPC Support ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-haswell-ipc.h" ++++++#include "sst-dsp.h" ++++++#include "sst-dsp-priv.h" ++++++ ++++++/* Global Message - Generic */ ++++++#define IPC_GLB_TYPE_SHIFT 24 ++++++#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT) ++++++#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT) ++++++ ++++++/* Global Message - Reply */ ++++++#define IPC_GLB_REPLY_SHIFT 0 ++++++#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT) ++++++#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT) ++++++ ++++++/* Stream Message - Generic */ ++++++#define IPC_STR_TYPE_SHIFT 20 ++++++#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT) ++++++#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT) ++++++#define IPC_STR_ID_SHIFT 16 ++++++#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT) ++++++#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT) ++++++ ++++++/* Stream Message - Reply */ ++++++#define IPC_STR_REPLY_SHIFT 0 ++++++#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT) ++++++ ++++++/* Stream Stage Message - Generic */ ++++++#define IPC_STG_TYPE_SHIFT 12 ++++++#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT) ++++++#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT) ++++++#define IPC_STG_ID_SHIFT 10 ++++++#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT) ++++++#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT) ++++++ ++++++/* Stream Stage Message - Reply */ ++++++#define IPC_STG_REPLY_SHIFT 0 ++++++#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT) ++++++ ++++++/* Debug Log Message - Generic */ ++++++#define IPC_LOG_OP_SHIFT 20 ++++++#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT) ++++++#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT) ++++++#define IPC_LOG_ID_SHIFT 16 ++++++#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) ++++++#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) ++++++ ++++++/* IPC message timeout (msecs) */ ++++++#define IPC_TIMEOUT_MSECS 300 ++++++#define IPC_BOOT_MSECS 200 ++++++#define IPC_MSG_WAIT 0 ++++++#define IPC_MSG_NOWAIT 1 ++++++ ++++++/* Firmware Ready Message */ ++++++#define IPC_FW_READY (0x1 << 29) ++++++#define IPC_STATUS_MASK (0x3 << 30) ++++++ ++++++#define IPC_EMPTY_LIST_SIZE 8 ++++++#define IPC_MAX_STREAMS 4 ++++++ ++++++/* Mailbox */ ++++++#define IPC_MAX_MAILBOX_BYTES 256 ++++++ ++++++/* Global Message - Types and Replies */ ++++++enum ipc_glb_type { ++++++ IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ ++++++ IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */ ++++++ IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */ ++++++ IPC_GLB_FREE_STREAM = 4, /* Request to free stream */ ++++++ IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */ ++++++ IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */ ++++++ /* Request to store firmware context during D0->D3 transition */ ++++++ IPC_GLB_REQUEST_DUMP = 7, ++++++ /* Request to restore firmware context during D3->D0 transition */ ++++++ IPC_GLB_RESTORE_CONTEXT = 8, ++++++ IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */ ++++++ IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */ ++++++ IPC_GLB_SHORT_REPLY = 11, ++++++ IPC_GLB_ENTER_DX_STATE = 12, ++++++ IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ ++++++ IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ ++++++ IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ ++++++ IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ ++++++}; ++++++ ++++++enum ipc_glb_reply { ++++++ IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */ ++++++ IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */ ++++++ IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */ ++++++ IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */ ++++++ IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */ ++++++ IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */ ++++++ IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */ ++++++ IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */ ++++++ IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */ ++++++ IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */ ++++++ IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ ++++++}; ++++++ ++++++/* Stream Message - Types */ ++++++enum ipc_str_operation { ++++++ IPC_STR_RESET = 0, ++++++ IPC_STR_PAUSE = 1, ++++++ IPC_STR_RESUME = 2, ++++++ IPC_STR_STAGE_MESSAGE = 3, ++++++ IPC_STR_NOTIFICATION = 4, ++++++ IPC_STR_MAX_MESSAGE ++++++}; ++++++ ++++++/* Stream Stage Message Types */ ++++++enum ipc_stg_operation { ++++++ IPC_STG_GET_VOLUME = 0, ++++++ IPC_STG_SET_VOLUME, ++++++ IPC_STG_SET_WRITE_POSITION, ++++++ IPC_STG_SET_FX_ENABLE, ++++++ IPC_STG_SET_FX_DISABLE, ++++++ IPC_STG_SET_FX_GET_PARAM, ++++++ IPC_STG_SET_FX_SET_PARAM, ++++++ IPC_STG_SET_FX_GET_INFO, ++++++ IPC_STG_MUTE_LOOPBACK, ++++++ IPC_STG_MAX_MESSAGE ++++++}; ++++++ ++++++/* Stream Stage Message Types For Notification*/ ++++++enum ipc_stg_operation_notify { ++++++ IPC_POSITION_CHANGED = 0, ++++++ IPC_STG_GLITCH, ++++++ IPC_STG_MAX_NOTIFY ++++++}; ++++++ ++++++enum ipc_glitch_type { ++++++ IPC_GLITCH_UNDERRUN = 1, ++++++ IPC_GLITCH_DECODER_ERROR, ++++++ IPC_GLITCH_DOUBLED_WRITE_POS, ++++++ IPC_GLITCH_MAX ++++++}; ++++++ ++++++/* Debug Control */ ++++++enum ipc_debug_operation { ++++++ IPC_DEBUG_ENABLE_LOG = 0, ++++++ IPC_DEBUG_DISABLE_LOG = 1, ++++++ IPC_DEBUG_REQUEST_LOG_DUMP = 2, ++++++ IPC_DEBUG_NOTIFY_LOG_DUMP = 3, ++++++ IPC_DEBUG_MAX_DEBUG_LOG ++++++}; ++++++ ++++++/* Firmware Ready */ ++++++struct sst_hsw_ipc_fw_ready { ++++++ u32 inbox_offset; ++++++ u32 outbox_offset; ++++++ u32 inbox_size; ++++++ u32 outbox_size; ++++++ u32 fw_info_size; ++++++ u8 fw_info[1]; ++++++} __attribute__((packed)); ++++++ ++++++struct ipc_message { ++++++ struct list_head list; ++++++ u32 header; ++++++ ++++++ /* direction wrt host CPU */ ++++++ char tx_data[IPC_MAX_MAILBOX_BYTES]; ++++++ size_t tx_size; ++++++ char rx_data[IPC_MAX_MAILBOX_BYTES]; ++++++ size_t rx_size; ++++++ ++++++ wait_queue_head_t waitq; ++++++ bool pending; ++++++ bool complete; ++++++ bool wait; ++++++ int errno; ++++++}; ++++++ ++++++struct sst_hsw_stream; ++++++struct sst_hsw; ++++++ ++++++/* Stream infomation */ ++++++struct sst_hsw_stream { ++++++ /* configuration */ ++++++ struct sst_hsw_ipc_stream_alloc_req request; ++++++ struct sst_hsw_ipc_stream_alloc_reply reply; ++++++ struct sst_hsw_ipc_stream_free_req free_req; ++++++ ++++++ /* Mixer info */ ++++++ u32 mute_volume[SST_HSW_NO_CHANNELS]; ++++++ u32 mute[SST_HSW_NO_CHANNELS]; ++++++ ++++++ /* runtime info */ ++++++ struct sst_hsw *hsw; ++++++ int host_id; ++++++ bool commited; ++++++ bool running; ++++++ ++++++ /* Notification work */ ++++++ struct work_struct notify_work; ++++++ u32 header; ++++++ ++++++ /* Position info from DSP */ ++++++ struct sst_hsw_ipc_stream_set_position wpos; ++++++ struct sst_hsw_ipc_stream_get_position rpos; ++++++ struct sst_hsw_ipc_stream_glitch_position glitch; ++++++ ++++++ /* Volume info */ ++++++ struct sst_hsw_ipc_volume_req vol_req; ++++++ ++++++ /* driver callback */ ++++++ u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); ++++++ void *pdata; ++++++ ++++++ struct list_head node; ++++++}; ++++++ ++++++/* FW log ring information */ ++++++struct sst_hsw_log_stream { ++++++ dma_addr_t dma_addr; ++++++ unsigned char *dma_area; ++++++ unsigned char *ring_descr; ++++++ int pages; ++++++ int size; ++++++ ++++++ /* Notification work */ ++++++ struct work_struct notify_work; ++++++ wait_queue_head_t readers_wait_q; ++++++ struct mutex rw_mutex; ++++++ ++++++ u32 last_pos; ++++++ u32 curr_pos; ++++++ u32 reader_pos; ++++++ ++++++ /* fw log config */ ++++++ u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; ++++++ ++++++ struct sst_hsw *hsw; ++++++}; ++++++ ++++++/* SST Haswell IPC data */ ++++++struct sst_hsw { ++++++ struct device *dev; ++++++ struct sst_dsp *dsp; ++++++ struct platform_device *pdev_pcm; ++++++ ++++++ /* FW config */ ++++++ struct sst_hsw_ipc_fw_ready fw_ready; ++++++ struct sst_hsw_ipc_fw_version version; ++++++ struct sst_module *scratch; ++++++ bool fw_done; ++++++ ++++++ /* stream */ ++++++ struct list_head stream_list; ++++++ ++++++ /* global mixer */ ++++++ struct sst_hsw_ipc_stream_info_reply mixer_info; ++++++ enum sst_hsw_volume_curve curve_type; ++++++ u32 curve_duration; ++++++ u32 mute[SST_HSW_NO_CHANNELS]; ++++++ u32 mute_volume[SST_HSW_NO_CHANNELS]; ++++++ ++++++ /* DX */ ++++++ struct sst_hsw_ipc_dx_reply dx; ++++++ ++++++ /* boot */ ++++++ wait_queue_head_t boot_wait; ++++++ bool boot_complete; ++++++ bool shutdown; ++++++ ++++++ /* IPC messaging */ ++++++ struct list_head tx_list; ++++++ struct list_head rx_list; ++++++ struct list_head empty_list; ++++++ wait_queue_head_t wait_txq; ++++++ struct task_struct *tx_thread; ++++++ struct kthread_worker kworker; ++++++ struct kthread_work kwork; ++++++ bool pending; ++++++ struct ipc_message *msg; ++++++ ++++++ /* FW log stream */ ++++++ struct sst_hsw_log_stream log_stream; ++++++}; ++++++ ++++++#define CREATE_TRACE_POINTS ++++++#include ++++++ ++++++static inline u32 msg_get_global_type(u32 msg) ++++++{ ++++++ return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; ++++++} ++++++ ++++++static inline u32 msg_get_global_reply(u32 msg) ++++++{ ++++++ return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; ++++++} ++++++ ++++++static inline u32 msg_get_stream_type(u32 msg) ++++++{ ++++++ return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; ++++++} ++++++ ++++++static inline u32 msg_get_stage_type(u32 msg) ++++++{ ++++++ return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; ++++++} ++++++ ++++++static inline u32 msg_set_stage_type(u32 msg, u32 type) ++++++{ ++++++ return (msg & ~IPC_STG_TYPE_MASK) + ++++++ (type << IPC_STG_TYPE_SHIFT); ++++++} ++++++ ++++++static inline u32 msg_get_stream_id(u32 msg) ++++++{ ++++++ return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; ++++++} ++++++ ++++++static inline u32 msg_get_notify_reason(u32 msg) ++++++{ ++++++ return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; ++++++} ++++++ ++++++u32 create_channel_map(enum sst_hsw_channel_config config) ++++++{ ++++++ switch (config) { ++++++ case SST_HSW_CHANNEL_CONFIG_MONO: ++++++ return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); ++++++ case SST_HSW_CHANNEL_CONFIG_STEREO: ++++++ return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_RIGHT << 4)); ++++++ case SST_HSW_CHANNEL_CONFIG_2_POINT_1: ++++++ return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_RIGHT << 4) ++++++ | (SST_HSW_CHANNEL_LFE << 8 )); ++++++ case SST_HSW_CHANNEL_CONFIG_3_POINT_0: ++++++ return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_CENTER << 4) ++++++ | (SST_HSW_CHANNEL_RIGHT << 8)); ++++++ case SST_HSW_CHANNEL_CONFIG_3_POINT_1: ++++++ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_CENTER << 4) ++++++ | (SST_HSW_CHANNEL_RIGHT << 8) ++++++ | (SST_HSW_CHANNEL_LFE << 12)); ++++++ case SST_HSW_CHANNEL_CONFIG_QUATRO: ++++++ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_RIGHT << 4) ++++++ | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) ++++++ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); ++++++ case SST_HSW_CHANNEL_CONFIG_4_POINT_0: ++++++ return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_CENTER << 4) ++++++ | (SST_HSW_CHANNEL_RIGHT << 8) ++++++ | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); ++++++ case SST_HSW_CHANNEL_CONFIG_5_POINT_0: ++++++ return (0xFFF00000 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_CENTER << 4) ++++++ | (SST_HSW_CHANNEL_RIGHT << 8) ++++++ | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) ++++++ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); ++++++ case SST_HSW_CHANNEL_CONFIG_5_POINT_1: ++++++ return (0xFF000000 | SST_HSW_CHANNEL_CENTER ++++++ | (SST_HSW_CHANNEL_LEFT << 4) ++++++ | (SST_HSW_CHANNEL_RIGHT << 8) ++++++ | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) ++++++ | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) ++++++ | (SST_HSW_CHANNEL_LFE << 20)); ++++++ case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: ++++++ return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT ++++++ | (SST_HSW_CHANNEL_LEFT << 4)); ++++++ default: ++++++ return 0xFFFFFFFF; ++++++ } ++++++} ++++++ ++++++static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, ++++++ int stream_id) ++++++{ ++++++ struct sst_hsw_stream *stream; ++++++ ++++++ list_for_each_entry(stream, &hsw->stream_list, node) { ++++++ if (stream->reply.stream_hw_id == stream_id) ++++++ return stream; ++++++ } ++++++ ++++++ return NULL; ++++++} ++++++ ++++++static void ipc_shim_dbg(struct sst_hsw *hsw, const char *text) ++++++{ ++++++ struct sst_dsp *sst = hsw->dsp; ++++++ u32 isr, ipcd, imrx, ipcx; ++++++ ++++++ ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); ++++++ isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); ++++++ ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); ++++++ imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); ++++++ ++++++ dev_err(hsw->dev, "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", ++++++ text, ipcx, isr, ipcd, imrx); ++++++} ++++++ ++++++/* locks held by caller */ ++++++static struct ipc_message *msg_get_empty(struct sst_hsw *hsw) ++++++{ ++++++ struct ipc_message *msg = NULL; ++++++ ++++++ if (!list_empty(&hsw->empty_list)) { ++++++ msg = list_first_entry(&hsw->empty_list, struct ipc_message, ++++++ list); ++++++ list_del(&msg->list); ++++++ } ++++++ ++++++ return msg; ++++++} ++++++ ++++++static void ipc_tx_msgs(struct kthread_work *work) ++++++{ ++++++ struct sst_hsw *hsw = ++++++ container_of(work, struct sst_hsw, kwork); ++++++ struct ipc_message *msg; ++++++ unsigned long flags; ++++++ u32 ipcx; ++++++ ++++++ spin_lock_irqsave(&hsw->dsp->spinlock, flags); ++++++ ++++++ if (list_empty(&hsw->tx_list) || hsw->pending) { ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++ return; ++++++ } ++++++ ++++++ /* if the DSP is busy we will TX messages after IRQ */ ++++++ ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); ++++++ if (ipcx & SST_IPCX_BUSY) { ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++ return; ++++++ } ++++++ ++++++ msg = list_first_entry(&hsw->tx_list, struct ipc_message, list); ++++++ ++++++ list_move(&msg->list, &hsw->rx_list); ++++++ ++++++ /* send the message */ ++++++ sst_dsp_outbox_write(hsw->dsp, msg->tx_data, msg->tx_size); ++++++ sst_dsp_ipc_msg_tx(hsw->dsp, msg->header | SST_IPCX_BUSY); ++++++ ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++} ++++++ ++++++/* locks held by caller */ ++++++static void tx_msg_reply_complete(struct sst_hsw *hsw, struct ipc_message *msg) ++++++{ ++++++ msg->complete = true; ++++++ trace_ipc_reply("completed", msg->header); ++++++ ++++++ if (!msg->wait) ++++++ list_add_tail(&msg->list, &hsw->empty_list); ++++++ else ++++++ wake_up(&msg->waitq); ++++++} ++++++ ++++++static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, ++++++ void *rx_data) ++++++{ ++++++ unsigned long flags; ++++++ int ret; ++++++ ++++++ /* wait for DSP completion (in all cases atm inc pending) */ ++++++ ret = wait_event_timeout(msg->waitq, msg->complete, ++++++ msecs_to_jiffies(IPC_TIMEOUT_MSECS)); ++++++ ++++++ spin_lock_irqsave(&hsw->dsp->spinlock, flags); ++++++ if (ret == 0) { ++++++ ipc_shim_dbg(hsw, "message timeout"); ++++++ ++++++ trace_ipc_error("error message timeout for", msg->header); ++++++ ret = -ETIMEDOUT; ++++++ } else { ++++++ ++++++ /* copy the data returned from DSP */ ++++++ if (msg->rx_size) ++++++ memcpy(rx_data, msg->rx_data, msg->rx_size); ++++++ ret = msg->errno; ++++++ } ++++++ ++++++ list_add_tail(&msg->list, &hsw->empty_list); ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++ return ret; ++++++} ++++++ ++++++static int ipc_tx_message(struct sst_hsw *hsw, u32 header, void *tx_data, ++++++ size_t tx_bytes, void *rx_data, size_t rx_bytes, int wait) ++++++{ ++++++ struct ipc_message *msg; ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&hsw->dsp->spinlock, flags); ++++++ ++++++ msg = msg_get_empty(hsw); ++++++ if (msg == NULL) { ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++ return -EBUSY; ++++++ } ++++++ ++++++ if (tx_bytes) ++++++ memcpy(msg->tx_data, tx_data, tx_bytes); ++++++ ++++++ msg->header = header; ++++++ msg->tx_size = tx_bytes; ++++++ msg->rx_size = rx_bytes; ++++++ msg->wait = wait; ++++++ msg->errno = 0; ++++++ msg->pending = false; ++++++ msg->complete = false; ++++++ ++++++ list_add_tail(&msg->list, &hsw->tx_list); ++++++ spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); ++++++ ++++++ queue_kthread_work(&hsw->kworker, &hsw->kwork); ++++++ ++++++ if (wait) ++++++ return tx_wait_done(hsw, msg, rx_data); ++++++ else ++++++ return 0; ++++++} ++++++ ++++++static inline int ipc_tx_message_wait(struct sst_hsw *hsw, u32 header, ++++++ void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) ++++++{ ++++++ return ipc_tx_message(hsw, header, tx_data, tx_bytes, rx_data, ++++++ rx_bytes, 1); ++++++} ++++++ ++++++static inline int ipc_tx_message_nowait(struct sst_hsw *hsw, u32 header, ++++++ void *tx_data, size_t tx_bytes) ++++++{ ++++++ return ipc_tx_message(hsw, header, tx_data, tx_bytes, NULL, 0, 0); ++++++} ++++++ ++++++static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) ++++++{ ++++++ struct sst_hsw_ipc_fw_ready fw_ready; ++++++ u32 offset; ++++++ ++++++ offset = (header & 0x1FFFFFFF) << 3; ++++++ ++++++ dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", ++++++ header, offset); ++++++ ++++++ /* copy data from the DSP FW ready offset */ ++++++ sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); ++++++ ++++++ sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, ++++++ fw_ready.inbox_size, fw_ready.outbox_offset, ++++++ fw_ready.outbox_size); ++++++ ++++++ hsw->boot_complete = true; ++++++ wake_up(&hsw->boot_wait); ++++++ ++++++ dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", ++++++ fw_ready.inbox_offset, fw_ready.inbox_size); ++++++ dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", ++++++ fw_ready.outbox_offset, fw_ready.outbox_size); ++++++} ++++++ ++++++static void hsw_notification_work(struct work_struct *work) ++++++{ ++++++ struct sst_hsw_stream *stream = container_of(work, ++++++ struct sst_hsw_stream, notify_work); ++++++ struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; ++++++ struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; ++++++ struct sst_hsw *hsw = stream->hsw; ++++++ u32 reason; ++++++ ++++++ reason = msg_get_notify_reason(stream->header); ++++++ ++++++ switch (reason) { ++++++ case IPC_STG_GLITCH: ++++++ trace_ipc_notification("DSP stream under/overrun", ++++++ stream->reply.stream_hw_id); ++++++ sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); ++++++ ++++++ dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", ++++++ glitch->glitch_type, glitch->present_pos, ++++++ glitch->write_pos); ++++++ break; ++++++ ++++++ case IPC_POSITION_CHANGED: ++++++ trace_ipc_notification("DSP stream position changed for", ++++++ stream->reply.stream_hw_id); ++++++ sst_dsp_inbox_read(hsw->dsp, pos, sizeof(pos)); ++++++ ++++++ if (stream->notify_position) ++++++ stream->notify_position(stream, stream->pdata); ++++++ ++++++ break; ++++++ default: ++++++ dev_err(hsw->dev, "error: unknown notification 0x%x\n", ++++++ stream->header); ++++++ break; ++++++ } ++++++ ++++++ /* tell DSP that notification has been handled */ ++++++ sst_dsp_shim_update_bits_unlocked(hsw->dsp, SST_IPCD, ++++++ SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); ++++++ ++++++ /* unmask busy interrupt */ ++++++ sst_dsp_shim_update_bits_unlocked(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); ++++++} ++++++ ++++++static struct ipc_message *reply_find_msg(struct sst_hsw *hsw, u32 header) ++++++{ ++++++ struct ipc_message *msg; ++++++ ++++++ /* clear reply bits & status bits */ ++++++ header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); ++++++ ++++++ if (list_empty(&hsw->rx_list)) { ++++++ dev_err(hsw->dev, "error: rx list empty but received 0x%x\n", ++++++ header); ++++++ return NULL; ++++++ } ++++++ ++++++ list_for_each_entry(msg, &hsw->rx_list, list) { ++++++ if (msg->header == header) ++++++ return msg; ++++++ } ++++++ ++++++ return NULL; ++++++} ++++++ ++++++static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) ++++++{ ++++++ struct sst_hsw_stream *stream; ++++++ u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); ++++++ u32 stream_id = msg_get_stream_id(header); ++++++ u32 stream_msg = msg_get_stream_type(header); ++++++ ++++++ stream = get_stream_by_id(hsw, stream_id); ++++++ if (stream == NULL) ++++++ return; ++++++ ++++++ switch (stream_msg) { ++++++ case IPC_STR_STAGE_MESSAGE: ++++++ case IPC_STR_NOTIFICATION: ++++++ case IPC_STR_RESET: ++++++ break; ++++++ case IPC_STR_PAUSE: ++++++ stream->running = false; ++++++ trace_ipc_notification("stream paused", ++++++ stream->reply.stream_hw_id); ++++++ break; ++++++ case IPC_STR_RESUME: ++++++ stream->running = true; ++++++ trace_ipc_notification("stream running", ++++++ stream->reply.stream_hw_id); ++++++ break; ++++++ } ++++++} ++++++ ++++++static int hsw_process_reply(struct sst_hsw *hsw, u32 header) ++++++{ ++++++ struct ipc_message *msg; ++++++ u32 reply = msg_get_global_reply(header); ++++++ ++++++ trace_ipc_reply("processing -->", header); ++++++ ++++++ msg = reply_find_msg(hsw, header); ++++++ if (msg == NULL) { ++++++ trace_ipc_error("error: can't find message header", header); ++++++ return -EIO; ++++++ } ++++++ ++++++ /* first process the header */ ++++++ switch (reply) { ++++++ case IPC_GLB_REPLY_PENDING: ++++++ trace_ipc_pending_reply("received", header); ++++++ msg->pending = true; ++++++ hsw->pending = true; ++++++ return 1; ++++++ case IPC_GLB_REPLY_SUCCESS: ++++++ if (msg->pending) { ++++++ trace_ipc_pending_reply("completed", header); ++++++ sst_dsp_inbox_read(hsw->dsp, msg->rx_data, ++++++ msg->rx_size); ++++++ hsw->pending = false; ++++++ } else { ++++++ /* copy data from the DSP */ ++++++ sst_dsp_outbox_read(hsw->dsp, msg->rx_data, ++++++ msg->rx_size); ++++++ } ++++++ break; ++++++ /* these will be rare - but useful for debug */ ++++++ case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: ++++++ trace_ipc_error("error: unknown message type", header); ++++++ msg->errno = -EBADMSG; ++++++ break; ++++++ case IPC_GLB_REPLY_OUT_OF_RESOURCES: ++++++ trace_ipc_error("error: out of resources", header); ++++++ msg->errno = -ENOMEM; ++++++ break; ++++++ case IPC_GLB_REPLY_BUSY: ++++++ trace_ipc_error("error: reply busy", header); ++++++ msg->errno = -EBUSY; ++++++ break; ++++++ case IPC_GLB_REPLY_FAILURE: ++++++ trace_ipc_error("error: reply failure", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ case IPC_GLB_REPLY_STAGE_UNINITIALIZED: ++++++ trace_ipc_error("error: stage uninitialized", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ case IPC_GLB_REPLY_NOT_FOUND: ++++++ trace_ipc_error("error: reply not found", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ case IPC_GLB_REPLY_SOURCE_NOT_STARTED: ++++++ trace_ipc_error("error: source not started", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ case IPC_GLB_REPLY_INVALID_REQUEST: ++++++ trace_ipc_error("error: invalid request", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ case IPC_GLB_REPLY_ERROR_INVALID_PARAM: ++++++ trace_ipc_error("error: invalid parameter", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ default: ++++++ trace_ipc_error("error: unknown reply", header); ++++++ msg->errno = -EINVAL; ++++++ break; ++++++ } ++++++ ++++++ /* update any stream states */ ++++++ hsw_stream_update(hsw, msg); ++++++ ++++++ /* wake up and return the error if we have waiters on this message ? */ ++++++ list_del(&msg->list); ++++++ tx_msg_reply_complete(hsw, msg); ++++++ ++++++ return 1; ++++++} ++++++ ++++++static int hsw_stream_message(struct sst_hsw *hsw, u32 header) ++++++{ ++++++ u32 stream_msg, stream_id, stage_type; ++++++ struct sst_hsw_stream *stream; ++++++ int handled = 0; ++++++ ++++++ stream_msg = msg_get_stream_type(header); ++++++ stream_id = msg_get_stream_id(header); ++++++ stage_type = msg_get_stage_type(header); ++++++ ++++++ stream = get_stream_by_id(hsw, stream_id); ++++++ if (stream == NULL) ++++++ return handled; ++++++ ++++++ stream->header = header; ++++++ ++++++ switch (stream_msg) { ++++++ case IPC_STR_STAGE_MESSAGE: ++++++ dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", ++++++ header); ++++++ break; ++++++ case IPC_STR_NOTIFICATION: ++++++ schedule_work(&stream->notify_work); ++++++ break; ++++++ default: ++++++ /* handle pending message complete request */ ++++++ handled = hsw_process_reply(hsw, header); ++++++ break; ++++++ } ++++++ ++++++ return handled; ++++++} ++++++ ++++++static int hsw_log_message(struct sst_hsw *hsw, u32 header) ++++++{ ++++++ u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; ++++++ struct sst_hsw_log_stream *stream = &hsw->log_stream; ++++++ int ret = 1; ++++++ ++++++ if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { ++++++ dev_err(hsw->dev, ++++++ "error: log msg not implemented 0x%8.8x\n", header); ++++++ return 0; ++++++ } ++++++ ++++++ mutex_lock(&stream->rw_mutex); ++++++ stream->last_pos = stream->curr_pos; ++++++ sst_dsp_inbox_read( ++++++ hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos)); ++++++ mutex_unlock(&stream->rw_mutex); ++++++ ++++++ schedule_work(&stream->notify_work); ++++++ ++++++ return ret; ++++++} ++++++ ++++++static int hsw_process_notification(struct sst_hsw *hsw) ++++++{ ++++++ struct sst_dsp *sst = hsw->dsp; ++++++ u32 type, header; ++++++ int handled = 1; ++++++ ++++++ header = sst_dsp_shim_read_unlocked(sst, SST_IPCD); ++++++ type = msg_get_global_type(header); ++++++ ++++++ trace_ipc_request("processing -->", header); ++++++ ++++++ /* FW Ready is a special case */ ++++++ if (!hsw->boot_complete && header & IPC_FW_READY) { ++++++ hsw_fw_ready(hsw, header); ++++++ return handled; ++++++ } ++++++ ++++++ switch (type) { ++++++ case IPC_GLB_GET_FW_VERSION: ++++++ case IPC_GLB_ALLOCATE_STREAM: ++++++ case IPC_GLB_FREE_STREAM: ++++++ case IPC_GLB_GET_FW_CAPABILITIES: ++++++ case IPC_GLB_REQUEST_DUMP: ++++++ case IPC_GLB_GET_DEVICE_FORMATS: ++++++ case IPC_GLB_SET_DEVICE_FORMATS: ++++++ case IPC_GLB_ENTER_DX_STATE: ++++++ case IPC_GLB_GET_MIXER_STREAM_INFO: ++++++ case IPC_GLB_MAX_IPC_MESSAGE_TYPE: ++++++ case IPC_GLB_RESTORE_CONTEXT: ++++++ case IPC_GLB_SHORT_REPLY: ++++++ dev_err(hsw->dev, "error: message type %d header 0x%x\n", ++++++ type, header); ++++++ break; ++++++ case IPC_GLB_STREAM_MESSAGE: ++++++ handled = hsw_stream_message(hsw, header); ++++++ break; ++++++ case IPC_GLB_DEBUG_LOG_MESSAGE: ++++++ handled = hsw_log_message(hsw, header); ++++++ break; ++++++ default: ++++++ dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", ++++++ type, header); ++++++ break; ++++++ } ++++++ ++++++ return handled; ++++++} ++++++ ++++++static irqreturn_t hsw_irq_thread(int irq, void *context) ++++++{ ++++++ struct sst_dsp *sst = (struct sst_dsp *) context; ++++++ struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); ++++++ u32 ipcx, ipcd; ++++++ int handled; ++++++ unsigned long flags; ++++++ ++++++ spin_lock_irqsave(&sst->spinlock, flags); ++++++ ++++++ ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); ++++++ ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); ++++++ ++++++ /* reply message from DSP */ ++++++ if (ipcx & SST_IPCX_DONE) { ++++++ ++++++ /* Handle Immediate reply from DSP Core */ ++++++ handled = hsw_process_reply(hsw, ipcx); ++++++ ++++++ if (handled > 0) { ++++++ /* clear DONE bit - tell DSP we have completed */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, ++++++ SST_IPCX_DONE, 0); ++++++ ++++++ /* unmask Done interrupt */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, ++++++ SST_IMRX_DONE, 0); ++++++ } ++++++ } ++++++ ++++++ /* new message from DSP */ ++++++ if (ipcd & SST_IPCD_BUSY) { ++++++ ++++++ /* Handle Notification and Delayed reply from DSP Core */ ++++++ handled = hsw_process_notification(hsw); ++++++ ++++++ /* clear BUSY bit and set DONE bit - accept new messages */ ++++++ if (handled > 0) { ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, ++++++ SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); ++++++ ++++++ /* unmask busy interrupt */ ++++++ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, ++++++ SST_IMRX_BUSY, 0); ++++++ } ++++++ } ++++++ ++++++ spin_unlock_irqrestore(&sst->spinlock, flags); ++++++ ++++++ /* continue to send any remaining messages... */ ++++++ queue_kthread_work(&hsw->kworker, &hsw->kwork); ++++++ ++++++ return IRQ_HANDLED; ++++++} ++++++ ++++++int sst_hsw_fw_get_version(struct sst_hsw *hsw, ++++++ struct sst_hsw_ipc_fw_version *version) ++++++{ ++++++ int ret; ++++++ ++++++ ret = ipc_tx_message_wait(hsw, IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), ++++++ NULL, 0, version, sizeof(*version)); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: get version failed\n"); ++++++ ++++++ return ret; ++++++} ++++++ ++++++/* Mixer Controls */ ++++++int sst_hsw_stream_mute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 stage_id, u32 channel) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_hsw_stream_get_volume(hsw, stream, stage_id, channel, ++++++ &stream->mute_volume[channel]); ++++++ if (ret < 0) ++++++ return ret; ++++++ ++++++ ret = sst_hsw_stream_set_volume(hsw, stream, stage_id, channel, 0); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: can't unmute stream %d channel %d\n", ++++++ stream->reply.stream_hw_id, channel); ++++++ return ret; ++++++ } ++++++ ++++++ stream->mute[channel] = 1; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_unmute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 stage_id, u32 channel) ++++++ ++++++{ ++++++ int ret; ++++++ ++++++ stream->mute[channel] = 0; ++++++ ret = sst_hsw_stream_set_volume(hsw, stream, stage_id, channel, ++++++ stream->mute_volume[channel]); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: can't unmute stream %d channel %d\n", ++++++ stream->reply.stream_hw_id, channel); ++++++ return ret; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 stage_id, u32 channel, u32 *volume) ++++++{ ++++++ if (channel > 1) ++++++ return -EINVAL; ++++++ ++++++ sst_dsp_read(hsw->dsp, volume, ++++++ stream->reply.volume_register_address[channel], sizeof(volume)); ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_volume_curve(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u64 curve_duration, ++++++ enum sst_hsw_volume_curve curve) ++++++{ ++++++ /* curve duration in steps of 100ns */ ++++++ stream->vol_req.curve_duration = curve_duration; ++++++ stream->vol_req.curve_type = curve; ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* stream volume */ ++++++int sst_hsw_stream_set_volume(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) ++++++{ ++++++ struct sst_hsw_ipc_volume_req *req; ++++++ u32 header; ++++++ int ret; ++++++ ++++++ trace_ipc_request("set stream volume", stream->reply.stream_hw_id); ++++++ ++++++ if (channel > 1) ++++++ return -EINVAL; ++++++ ++++++ if (stream->mute[channel]) { ++++++ stream->mute_volume[channel] = volume; ++++++ return 0; ++++++ } ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | ++++++ IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); ++++++ header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); ++++++ header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); ++++++ header |= (stage_id << IPC_STG_ID_SHIFT); ++++++ ++++++ req = &stream->vol_req; ++++++ req->channel = channel; ++++++ req->target_volume = volume; ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: set stream volume failed\n"); ++++++ return ret; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_mixer_mute(struct sst_hsw *hsw, u32 stage_id, u32 channel) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_hsw_mixer_get_volume(hsw, stage_id, channel, ++++++ &hsw->mute_volume[channel]); ++++++ if (ret < 0) ++++++ return ret; ++++++ ++++++ ret = sst_hsw_mixer_set_volume(hsw, stage_id, channel, 0); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: failed to unmute mixer channel %d\n", ++++++ channel); ++++++ return ret; ++++++ } ++++++ ++++++ hsw->mute[channel] = 1; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_mixer_unmute(struct sst_hsw *hsw, u32 stage_id, u32 channel) ++++++{ ++++++ int ret; ++++++ ++++++ ret = sst_hsw_mixer_set_volume(hsw, stage_id, channel, ++++++ hsw->mixer_info.volume_register_address[channel]); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: failed to unmute mixer channel %d\n", ++++++ channel); ++++++ return ret; ++++++ } ++++++ ++++++ hsw->mute[channel] = 0; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ++++++ u32 *volume) ++++++{ ++++++ if (channel > 1) ++++++ return -EINVAL; ++++++ ++++++ sst_dsp_read(hsw->dsp, volume, ++++++ hsw->mixer_info.volume_register_address[channel], ++++++ sizeof(*volume)); ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_mixer_set_volume_curve(struct sst_hsw *hsw, ++++++ u64 curve_duration, enum sst_hsw_volume_curve curve) ++++++{ ++++++ /* curve duration in steps of 100ns */ ++++++ hsw->curve_duration = curve_duration; ++++++ hsw->curve_type = curve; ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* global mixer volume */ ++++++int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ++++++ u32 volume) ++++++{ ++++++ struct sst_hsw_ipc_volume_req req; ++++++ u32 header; ++++++ int ret; ++++++ ++++++ trace_ipc_request("set mixer volume", volume); ++++++ ++++++ /* set both at same time ? */ ++++++ if (channel == 2) { ++++++ if (hsw->mute[0] && hsw->mute[1]) { ++++++ hsw->mute_volume[0] = hsw->mute_volume[1] = volume; ++++++ return 0; ++++++ } else if (hsw->mute[0]) ++++++ req.channel = 1; ++++++ else if (hsw->mute[1]) ++++++ req.channel = 0; ++++++ else ++++++ req.channel = 0xffffffff; ++++++ } else { ++++++ /* set only 1 channel */ ++++++ if (hsw->mute[channel]) { ++++++ hsw->mute_volume[channel] = volume; ++++++ return 0; ++++++ } ++++++ req.channel = channel; ++++++ } ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | ++++++ IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); ++++++ header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); ++++++ header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); ++++++ header |= (stage_id << IPC_STG_ID_SHIFT); ++++++ ++++++ req.curve_duration = hsw->curve_duration; ++++++ req.curve_type = hsw->curve_type; ++++++ req.target_volume = volume; ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, &req, sizeof(req), NULL, 0); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: set mixer volume failed\n"); ++++++ return ret; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* Stream API */ ++++++struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, ++++++ u32 (*notify_position)(struct sst_hsw_stream *stream, void *data), ++++++ void *data) ++++++{ ++++++ struct sst_hsw_stream *stream; ++++++ ++++++ stream = kzalloc(sizeof(*stream), GFP_KERNEL); ++++++ if (stream == NULL) ++++++ return NULL; ++++++ ++++++ list_add(&stream->node, &hsw->stream_list); ++++++ stream->notify_position = notify_position; ++++++ stream->pdata = data; ++++++ stream->hsw = hsw; ++++++ stream->host_id = id; ++++++ ++++++ /* work to process notification messages */ ++++++ INIT_WORK(&stream->notify_work, hsw_notification_work); ++++++ ++++++ return stream; ++++++} ++++++ ++++++int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) ++++++{ ++++++ u32 header; ++++++ int ret = 0; ++++++ ++++++ /* dont free DSP streams that are not commited */ ++++++ if (!stream->commited) ++++++ goto out; ++++++ ++++++ trace_ipc_request("stream free", stream->host_id); ++++++ ++++++ stream->free_req.stream_id = stream->reply.stream_hw_id; ++++++ header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, &stream->free_req, ++++++ sizeof(stream->free_req), NULL, 0); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: free stream %d failed\n", ++++++ stream->free_req.stream_id); ++++++ return -EAGAIN; ++++++ } ++++++ ++++++ trace_hsw_stream_free_req(stream, &stream->free_req); ++++++ ++++++out: ++++++ list_del(&stream->node); ++++++ kfree(stream); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_hsw_stream_set_bits(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set bits\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.format.bitdepth = bits; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_channels(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, int channels) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set channels\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ /* stereo is only supported atm */ ++++++ if (channels != 2) ++++++ return -EINVAL; ++++++ ++++++ stream->request.format.ch_num = channels; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_rate(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, int rate) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set rate\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.format.frequency = rate; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 map, ++++++ enum sst_hsw_channel_config config) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set map\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.format.map = map; ++++++ stream->request.format.config = config; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_style(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, enum sst_hsw_interleaving style) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set style\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.format.style = style; ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_valid(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 bits) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set valid bits\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.format.valid_bit = bits; ++++++ return 0; ++++++} ++++++ ++++++/* Stream Configuration */ ++++++int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ enum sst_hsw_stream_path_id path_id, ++++++ enum sst_hsw_stream_type stream_type, ++++++ enum sst_hsw_stream_format format_id) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set format\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.path_id = path_id; ++++++ stream->request.stream_type = stream_type; ++++++ stream->request.format_id = format_id; ++++++ ++++++ trace_hsw_stream_alloc_request(stream, &stream->request); ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 ring_pt_address, u32 num_pages, ++++++ u32 ring_size, u32 ring_offset, u32 ring_first_pfn) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for buffer\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.ringinfo.ring_pt_address = ring_pt_address; ++++++ stream->request.ringinfo.num_pages = num_pages; ++++++ stream->request.ringinfo.ring_size = ring_size; ++++++ stream->request.ringinfo.ring_offset = ring_offset; ++++++ stream->request.ringinfo.ring_first_pfn = ring_first_pfn; ++++++ ++++++ trace_hsw_stream_buffer(stream); ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, ++++++ u32 entry_point) ++++++{ ++++++ struct sst_hsw_module_map *map = &stream->request.map; ++++++ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set module\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ /* only support initial module atm */ ++++++ map->module_entries_count = 1; ++++++ map->module_entries[0].module_id = module_id; ++++++ map->module_entries[0].entry_point = entry_point; ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 offset, u32 size) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set pmem\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.persistent_mem.offset = offset; ++++++ stream->request.persistent_mem.size = size; ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 offset, u32 size) ++++++{ ++++++ if (stream->commited) { ++++++ dev_err(hsw->dev, "error: stream committed for set smem\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ stream->request.scratch_mem.offset = offset; ++++++ stream->request.scratch_mem.size = size; ++++++ ++++++ return 0; ++++++} ++++++ ++++++int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) ++++++{ ++++++ struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request; ++++++ struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply; ++++++ u32 header; ++++++ int ret; ++++++ ++++++ trace_ipc_request("stream alloc", stream->host_id); ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, str_req, sizeof(*str_req), ++++++ reply, sizeof(*reply)); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: stream commit failed\n"); ++++++ return ret; ++++++ } ++++++ ++++++ stream->commited = 1; ++++++ trace_hsw_stream_alloc_reply(stream); ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* Stream Information - these calls could be inline but we want the IPC ++++++ ABI to be opaque to client PCM drivers to cope with any future ABI changes */ ++++++int sst_hsw_stream_get_hw_id(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream) ++++++{ ++++++ return stream->reply.stream_hw_id; ++++++} ++++++ ++++++int sst_hsw_stream_get_mixer_id(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream) ++++++{ ++++++ return stream->reply.mixer_hw_id; ++++++} ++++++ ++++++u32 sst_hsw_stream_get_read_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream) ++++++{ ++++++ return stream->reply.read_position_register_address; ++++++} ++++++ ++++++u32 sst_hsw_stream_get_pointer_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream) ++++++{ ++++++ return stream->reply.presentation_position_register_address; ++++++} ++++++ ++++++u32 sst_hsw_stream_get_peak_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 channel) ++++++{ ++++++ if (channel >= 2) ++++++ return 0; ++++++ ++++++ return stream->reply.peak_meter_register_address[channel]; ++++++} ++++++ ++++++u32 sst_hsw_stream_get_vol_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 channel) ++++++{ ++++++ if (channel >= 2) ++++++ return 0; ++++++ ++++++ return stream->reply.volume_register_address[channel]; ++++++} ++++++ ++++++int sst_hsw_mixer_get_info(struct sst_hsw *hsw) ++++++{ ++++++ struct sst_hsw_ipc_stream_info_reply *reply; ++++++ u32 header; ++++++ int ret; ++++++ ++++++ reply = &hsw->mixer_info; ++++++ header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); ++++++ ++++++ trace_ipc_request("get global mixer info", 0); ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, NULL, 0, reply, sizeof(*reply)); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: get stream info failed\n"); ++++++ return ret; ++++++ } ++++++ ++++++ trace_hsw_mixer_info_reply(reply); ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* Send stream command */ ++++++static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, ++++++ int stream_id, int wait) ++++++{ ++++++ u32 header; ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); ++++++ header |= (stream_id << IPC_STR_ID_SHIFT); ++++++ ++++++ if (wait) ++++++ return ipc_tx_message_wait(hsw, header, NULL, 0, NULL, 0); ++++++ else ++++++ return ipc_tx_message_nowait(hsw, header, NULL, 0); ++++++} ++++++ ++++++/* Stream ALSA trigger operations */ ++++++int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ int wait) ++++++{ ++++++ int ret; ++++++ ++++++ trace_ipc_request("stream pause", stream->reply.stream_hw_id); ++++++ ++++++ ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE, ++++++ stream->reply.stream_hw_id, wait); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: failed to pause stream %d\n", ++++++ stream->reply.stream_hw_id); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ int wait) ++++++{ ++++++ int ret; ++++++ ++++++ trace_ipc_request("stream resume", stream->reply.stream_hw_id); ++++++ ++++++ ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME, ++++++ stream->reply.stream_hw_id, wait); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: failed to resume stream %d\n", ++++++ stream->reply.stream_hw_id); ++++++ ++++++ return ret; ++++++} ++++++ ++++++int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream) ++++++{ ++++++ int ret, tries = 10; ++++++ ++++++ /* dont reset streams that are not commited */ ++++++ if (!stream->commited) ++++++ return 0; ++++++ ++++++ /* wait for pause to complete before we reset the stream */ ++++++ while (stream->running && tries--) ++++++ msleep(1); ++++++ if (!tries) { ++++++ dev_err(hsw->dev, "error: reset stream %d still running\n", ++++++ stream->reply.stream_hw_id); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ trace_ipc_request("stream reset", stream->reply.stream_hw_id); ++++++ ++++++ ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET, ++++++ stream->reply.stream_hw_id, 1); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: failed to reset stream %d\n", ++++++ stream->reply.stream_hw_id); ++++++ return ret; ++++++} ++++++ ++++++/* Stream pointer positions */ ++++++int sst_hsw_get_dsp_position(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream) ++++++{ ++++++ return stream->rpos.position; ++++++} ++++++ ++++++int sst_hsw_stream_set_write_position(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 stage_id, u32 position) ++++++{ ++++++ u32 header; ++++++ int ret; ++++++ ++++++ trace_stream_write_position(stream->reply.stream_hw_id, position); ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | ++++++ IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); ++++++ header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); ++++++ header |= (IPC_STG_SET_WRITE_POSITION << IPC_STG_TYPE_SHIFT); ++++++ header |= (stage_id << IPC_STG_ID_SHIFT); ++++++ stream->wpos.position = position; ++++++ ++++++ ret = ipc_tx_message_nowait(hsw, header, &stream->wpos, ++++++ sizeof(stream->wpos)); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: stream %d set position %d failed\n", ++++++ stream->reply.stream_hw_id, position); ++++++ ++++++ return ret; ++++++} ++++++ ++++++/* physical BE config */ ++++++int sst_hsw_device_set_config(struct sst_hsw *hsw, ++++++ enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, ++++++ enum sst_hsw_device_mode mode, u32 clock_divider) ++++++{ ++++++ struct sst_hsw_ipc_device_config_req config; ++++++ u32 header; ++++++ int ret; ++++++ ++++++ trace_ipc_request("set device config", dev); ++++++ ++++++ config.ssp_interface = dev; ++++++ config.clock_frequency = mclk; ++++++ config.mode = mode; ++++++ config.clock_divider = clock_divider; ++++++ ++++++ trace_hsw_device_config_req(&config); ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, &config, sizeof(config), ++++++ NULL, 0); ++++++ if (ret < 0) ++++++ dev_err(hsw->dev, "error: set device formats failed\n"); ++++++ ++++++ return ret; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); ++++++ ++++++/* DX Config */ ++++++int sst_hsw_dx_set_state(struct sst_hsw *hsw, ++++++ enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) ++++++{ ++++++ u32 header, state_; ++++++ int ret; ++++++ ++++++ header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); ++++++ state_ = state; ++++++ ++++++ trace_ipc_request("PM enter Dx state", state); ++++++ ++++++ ret = ipc_tx_message_wait(hsw, header, &state_, sizeof(state_), ++++++ dx, sizeof(dx)); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); ++++++ return ret; ++++++ } ++++++ ++++++ dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", ++++++ dx->entries_no, state); ++++++ ++++++ memcpy(&hsw->dx, dx, sizeof(*dx)); ++++++ return 0; ++++++} ++++++ ++++++/* Used to save state into hsw->dx_reply */ ++++++int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, ++++++ u32 *offset, u32 *size, u32 *source) ++++++{ ++++++ struct sst_hsw_ipc_dx_memory_item *dx_mem; ++++++ struct sst_hsw_ipc_dx_reply *dx_reply; ++++++ int entry_no; ++++++ ++++++ dx_reply = &hsw->dx; ++++++ entry_no = dx_reply->entries_no; ++++++ ++++++ trace_ipc_request("PM get Dx state", entry_no); ++++++ ++++++ if (item >= entry_no) ++++++ return -EINVAL; ++++++ ++++++ dx_mem = &dx_reply->mem_info[item]; ++++++ *offset = dx_mem->offset; ++++++ *size = dx_mem->size; ++++++ *source = dx_mem->source; ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int msg_empty_list_init(struct sst_hsw *hsw) ++++++{ ++++++ int i; ++++++ ++++++ hsw->msg = kzalloc(sizeof(struct ipc_message) * ++++++ IPC_EMPTY_LIST_SIZE, GFP_KERNEL); ++++++ if (hsw->msg == NULL) ++++++ return -ENOMEM; ++++++ ++++++ for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { ++++++ init_waitqueue_head(&hsw->msg[i].waitq); ++++++ list_add(&hsw->msg[i].list, &hsw->empty_list); ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++void sst_hsw_set_scratch_module(struct sst_hsw *hsw, ++++++ struct sst_module *scratch) ++++++{ ++++++ hsw->scratch = scratch; ++++++} ++++++ ++++++struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) ++++++{ ++++++ return hsw->dsp; ++++++} ++++++ ++++++static struct sst_dsp_device hsw_dev = { ++++++ .thread = hsw_irq_thread, ++++++ .ops = &haswell_ops, ++++++}; ++++++ ++++++int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) ++++++{ ++++++ struct sst_hsw_ipc_fw_version version; ++++++ struct sst_hsw *hsw; ++++++ struct sst_fw *hsw_sst_fw; ++++++ int ret; ++++++ ++++++ dev_dbg(dev, "initialising Audio DSP IPC\n"); ++++++ ++++++ hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); ++++++ if (hsw == NULL) ++++++ return -ENOMEM; ++++++ ++++++ hsw->dev = dev; ++++++ INIT_LIST_HEAD(&hsw->stream_list); ++++++ INIT_LIST_HEAD(&hsw->tx_list); ++++++ INIT_LIST_HEAD(&hsw->rx_list); ++++++ INIT_LIST_HEAD(&hsw->empty_list); ++++++ init_waitqueue_head(&hsw->boot_wait); ++++++ init_waitqueue_head(&hsw->wait_txq); ++++++ ++++++ ret = msg_empty_list_init(hsw); ++++++ if (ret < 0) ++++++ goto list_err; ++++++ ++++++ /* start the IPC message thread */ ++++++ init_kthread_worker(&hsw->kworker); ++++++ hsw->tx_thread = kthread_run(kthread_worker_fn, ++++++ &hsw->kworker, ++++++ dev_name(hsw->dev)); ++++++ if (IS_ERR(hsw->tx_thread)) { ++++++ ret = PTR_ERR(hsw->tx_thread); ++++++ dev_err(hsw->dev, "error: failed to create message TX task\n"); ++++++ goto list_err; ++++++ } ++++++ init_kthread_work(&hsw->kwork, ipc_tx_msgs); ++++++ ++++++ hsw_dev.thread_context = hsw; ++++++ ++++++ /* init SST shim */ ++++++ hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); ++++++ if (hsw->dsp == NULL) { ++++++ ret = -ENODEV; ++++++ goto list_err; ++++++ } ++++++ ++++++ /* keep the DSP in reset state for base FW loading */ ++++++ sst_dsp_reset(hsw->dsp); ++++++ ++++++ hsw_sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); ++++++ ++++++ if (hsw_sst_fw == NULL) { ++++++ ret = -ENODEV; ++++++ dev_err(dev, "error: failed to load firmware\n"); ++++++ goto fw_err; ++++++ } ++++++ ++++++ /* wait for DSP boot completion */ ++++++ sst_dsp_boot(hsw->dsp); ++++++ ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, ++++++ msecs_to_jiffies(IPC_BOOT_MSECS)); ++++++ if (ret == 0) { ++++++ ret = -EIO; ++++++ dev_err(hsw->dev, "error: ADSP boot timeout\n"); ++++++ goto boot_err; ++++++ } ++++++ ++++++ /* get the FW version */ ++++++ sst_hsw_fw_get_version(hsw, &version); ++++++ dev_info(hsw->dev, "FW loaded: type %d - version: %d.%d build %d\n", ++++++ version.type, version.major, version.minor, version.build); ++++++ ++++++ /* get the globalmixer */ ++++++ ret = sst_hsw_mixer_get_info(hsw); ++++++ if (ret < 0) { ++++++ dev_err(hsw->dev, "error: failed to get stream info\n"); ++++++ goto boot_err; ++++++ } ++++++ ++++++ pdata->dsp = hsw; ++++++ return 0; ++++++ ++++++boot_err: ++++++ sst_dsp_reset(hsw->dsp); ++++++ sst_fw_free(hsw_sst_fw); ++++++fw_err: ++++++ sst_dsp_free(hsw->dsp); ++++++ kfree(hsw->msg); ++++++list_err: ++++++ return ret; ++++++} ++++++EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); ++++++ ++++++void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) ++++++{ ++++++ struct sst_hsw *hsw = pdata->dsp; ++++++ ++++++ sst_dsp_reset(hsw->dsp); ++++++ sst_fw_free_all(hsw->dsp); ++++++ sst_dsp_free(hsw->dsp); ++++++ kfree(hsw->scratch); ++++++ kfree(hsw->msg); ++++++} ++++++EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --cc sound/soc/intel/sst-haswell-ipc.h index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..d517929ccc38 new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-haswell-ipc.h @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,488 @@@@@@@ ++++++/* ++++++ * Intel SST Haswell/Broadwell IPC Support ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#ifndef __SST_HASWELL_IPC_H ++++++#define __SST_HASWELL_IPC_H ++++++ ++++++#include ++++++#include ++++++#include ++++++ ++++++#define SST_HSW_NO_CHANNELS 2 ++++++#define SST_HSW_MAX_DX_REGIONS 14 ++++++ ++++++#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 ++++++#define SST_HSW_GLOBAL_LOG 15 ++++++ ++++++/** ++++++ * Upfront defined maximum message size that is ++++++ * expected by the in/out communication pipes in FW. ++++++ */ ++++++#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 ++++++#define SST_HSW_MAX_INFO_SIZE 64 ++++++#define SST_HSW_BUILD_HASH_LENGTH 40 ++++++ ++++++struct sst_hsw; ++++++struct sst_hsw_stream; ++++++struct sst_hsw_log_stream; ++++++struct sst_pdata; ++++++struct sst_module; ++++++extern struct sst_ops haswell_ops; ++++++ ++++++/* Stream Allocate Path ID */ ++++++enum sst_hsw_stream_path_id { ++++++ SST_HSW_STREAM_PATH_SSP0_OUT = 0, ++++++ SST_HSW_STREAM_PATH_SSP0_IN = 1, ++++++ SST_HSW_STREAM_PATH_MAX_PATH_ID = 2, ++++++}; ++++++ ++++++/* Stream Allocate Stream Type */ ++++++enum sst_hsw_stream_type { ++++++ SST_HSW_STREAM_TYPE_RENDER = 0, ++++++ SST_HSW_STREAM_TYPE_SYSTEM = 1, ++++++ SST_HSW_STREAM_TYPE_CAPTURE = 2, ++++++ SST_HSW_STREAM_TYPE_LOOPBACK = 3, ++++++ SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4, ++++++}; ++++++ ++++++/* Stream Allocate Stream Format */ ++++++enum sst_hsw_stream_format { ++++++ SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0, ++++++ SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1, ++++++ SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2, ++++++ SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3, ++++++}; ++++++ ++++++/* Device ID */ ++++++enum sst_hsw_device_id { ++++++ SST_HSW_DEVICE_SSP_0 = 0, ++++++ SST_HSW_DEVICE_SSP_1 = 1, ++++++}; ++++++ ++++++/* Device Master Clock Frequency */ ++++++enum sst_hsw_device_mclk { ++++++ SST_HSW_DEVICE_MCLK_OFF = 0, ++++++ SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1, ++++++ SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2, ++++++ SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3, ++++++}; ++++++ ++++++/* Device Clock Master */ ++++++enum sst_hsw_device_mode { ++++++ SST_HSW_DEVICE_CLOCK_SLAVE = 0, ++++++ SST_HSW_DEVICE_CLOCK_MASTER = 1, ++++++}; ++++++ ++++++/* DX Power State */ ++++++enum sst_hsw_dx_state { ++++++ SST_HSW_DX_STATE_D0 = 0, ++++++ SST_HSW_DX_STATE_D1 = 1, ++++++ SST_HSW_DX_STATE_D3 = 3, ++++++ SST_HSW_DX_STATE_MAX = 3, ++++++}; ++++++ ++++++/* Audio stream stage IDs */ ++++++enum sst_hsw_fx_stage_id { ++++++ SST_HSW_STAGE_ID_WAVES = 0, ++++++ SST_HSW_STAGE_ID_DTS = 1, ++++++ SST_HSW_STAGE_ID_DOLBY = 2, ++++++ SST_HSW_STAGE_ID_BOOST = 3, ++++++ SST_HSW_STAGE_ID_MAX_FX_ID ++++++}; ++++++ ++++++/* DX State Type */ ++++++enum sst_hsw_dx_type { ++++++ SST_HSW_DX_TYPE_FW_IMAGE = 0, ++++++ SST_HSW_DX_TYPE_MEMORY_DUMP = 1 ++++++}; ++++++ ++++++/* Volume Curve Type*/ ++++++enum sst_hsw_volume_curve { ++++++ SST_HSW_VOLUME_CURVE_NONE = 0, ++++++ SST_HSW_VOLUME_CURVE_FADE = 1 ++++++}; ++++++ ++++++/* Sample ordering */ ++++++enum sst_hsw_interleaving { ++++++ SST_HSW_INTERLEAVING_PER_CHANNEL = 0, ++++++ SST_HSW_INTERLEAVING_PER_SAMPLE = 1, ++++++}; ++++++ ++++++/* Channel indices */ ++++++enum sst_hsw_channel_index { ++++++ SST_HSW_CHANNEL_LEFT = 0, ++++++ SST_HSW_CHANNEL_CENTER = 1, ++++++ SST_HSW_CHANNEL_RIGHT = 2, ++++++ SST_HSW_CHANNEL_LEFT_SURROUND = 3, ++++++ SST_HSW_CHANNEL_CENTER_SURROUND = 3, ++++++ SST_HSW_CHANNEL_RIGHT_SURROUND = 4, ++++++ SST_HSW_CHANNEL_LFE = 7, ++++++ SST_HSW_CHANNEL_INVALID = 0xF, ++++++}; ++++++ ++++++/* List of supported channel maps. */ ++++++enum sst_hsw_channel_config { ++++++ SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */ ++++++ SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */ ++++++ SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */ ++++++ SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */ ++++++ SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */ ++++++ SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */ ++++++ SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */ ++++++ SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */ ++++++ SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */ ++++++ SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */ ++++++ SST_HSW_CHANNEL_CONFIG_INVALID, ++++++}; ++++++ ++++++/* List of supported bit depths. */ ++++++enum sst_hsw_bitdepth { ++++++ SST_HSW_DEPTH_8BIT = 8, ++++++ SST_HSW_DEPTH_16BIT = 16, ++++++ SST_HSW_DEPTH_24BIT = 24, /* Default. */ ++++++ SST_HSW_DEPTH_32BIT = 32, ++++++ SST_HSW_DEPTH_INVALID = 33, ++++++}; ++++++ ++++++enum sst_hsw_module_id { ++++++ SST_HSW_MODULE_BASE_FW = 0x0, ++++++ SST_HSW_MODULE_MP3 = 0x1, ++++++ SST_HSW_MODULE_AAC_5_1 = 0x2, ++++++ SST_HSW_MODULE_AAC_2_0 = 0x3, ++++++ SST_HSW_MODULE_SRC = 0x4, ++++++ SST_HSW_MODULE_WAVES = 0x5, ++++++ SST_HSW_MODULE_DOLBY = 0x6, ++++++ SST_HSW_MODULE_BOOST = 0x7, ++++++ SST_HSW_MODULE_LPAL = 0x8, ++++++ SST_HSW_MODULE_DTS = 0x9, ++++++ SST_HSW_MODULE_PCM_CAPTURE = 0xA, ++++++ SST_HSW_MODULE_PCM_SYSTEM = 0xB, ++++++ SST_HSW_MODULE_PCM_REFERENCE = 0xC, ++++++ SST_HSW_MODULE_PCM = 0xD, ++++++ SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE, ++++++ SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF, ++++++ SST_HSW_MAX_MODULE_ID, ++++++}; ++++++ ++++++enum sst_hsw_performance_action { ++++++ SST_HSW_PERF_START = 0, ++++++ SST_HSW_PERF_STOP = 1, ++++++}; ++++++ ++++++/* SST firmware module info */ ++++++struct sst_hsw_module_info { ++++++ u8 name[SST_HSW_MAX_INFO_SIZE]; ++++++ u8 version[SST_HSW_MAX_INFO_SIZE]; ++++++} __attribute__((packed)); ++++++ ++++++/* Module entry point */ ++++++struct sst_hsw_module_entry { ++++++ enum sst_hsw_module_id module_id; ++++++ u32 entry_point; ++++++} __attribute__((packed)); ++++++ ++++++/* Module map - alignement matches DSP */ ++++++struct sst_hsw_module_map { ++++++ u8 module_entries_count; ++++++ struct sst_hsw_module_entry module_entries[1]; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_memory_info { ++++++ u32 offset; ++++++ u32 size; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_fx_enable { ++++++ struct sst_hsw_module_map module_map; ++++++ struct sst_hsw_memory_info persistent_mem; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_get_fx_param { ++++++ u32 parameter_id; ++++++ u32 param_size; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_perf_action { ++++++ u32 action; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_perf_data { ++++++ u64 timestamp; ++++++ u64 cycles; ++++++ u64 datatime; ++++++} __attribute__((packed)); ++++++ ++++++/* FW version */ ++++++struct sst_hsw_ipc_fw_version { ++++++ u8 build; ++++++ u8 minor; ++++++ u8 major; ++++++ u8 type; ++++++ u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH]; ++++++ u32 fw_log_providers_hash; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream ring info */ ++++++struct sst_hsw_ipc_stream_ring { ++++++ u32 ring_pt_address; ++++++ u32 num_pages; ++++++ u32 ring_size; ++++++ u32 ring_offset; ++++++ u32 ring_first_pfn; ++++++} __attribute__((packed)); ++++++ ++++++/* Debug Dump Log Enable Request */ ++++++struct sst_hsw_ipc_debug_log_enable_req { ++++++ struct sst_hsw_ipc_stream_ring ringinfo; ++++++ u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; ++++++} __attribute__((packed)); ++++++ ++++++/* Debug Dump Log Reply */ ++++++struct sst_hsw_ipc_debug_log_reply { ++++++ u32 log_buffer_begining; ++++++ u32 log_buffer_size; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream glitch position */ ++++++struct sst_hsw_ipc_stream_glitch_position { ++++++ u32 glitch_type; ++++++ u32 present_pos; ++++++ u32 write_pos; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream get position */ ++++++struct sst_hsw_ipc_stream_get_position { ++++++ u32 position; ++++++ u32 fw_cycle_count; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream set position */ ++++++struct sst_hsw_ipc_stream_set_position { ++++++ u32 position; ++++++ u32 end_of_buffer; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream Free Request */ ++++++struct sst_hsw_ipc_stream_free_req { ++++++ u8 stream_id; ++++++ u8 reserved[3]; ++++++} __attribute__((packed)); ++++++ ++++++/* Set Volume Request */ ++++++struct sst_hsw_ipc_volume_req { ++++++ u32 channel; ++++++ u32 target_volume; ++++++ u64 curve_duration; ++++++ u32 curve_type; ++++++} __attribute__((packed)); ++++++ ++++++/* Device Configuration Request */ ++++++struct sst_hsw_ipc_device_config_req { ++++++ u32 ssp_interface; ++++++ u32 clock_frequency; ++++++ u32 mode; ++++++ u16 clock_divider; ++++++ u16 reserved; ++++++} __attribute__((packed)); ++++++ ++++++/* Audio Data formats */ ++++++struct sst_hsw_audio_data_format_ipc { ++++++ u32 frequency; ++++++ u32 bitdepth; ++++++ u32 map; ++++++ u32 config; ++++++ u32 style; ++++++ u8 ch_num; ++++++ u8 valid_bit; ++++++ u8 reserved[2]; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream Allocate Request */ ++++++struct sst_hsw_ipc_stream_alloc_req { ++++++ u8 path_id; ++++++ u8 stream_type; ++++++ u8 format_id; ++++++ u8 reserved; ++++++ struct sst_hsw_audio_data_format_ipc format; ++++++ struct sst_hsw_ipc_stream_ring ringinfo; ++++++ struct sst_hsw_module_map map; ++++++ struct sst_hsw_memory_info persistent_mem; ++++++ struct sst_hsw_memory_info scratch_mem; ++++++ u32 number_of_notifications; ++++++} __attribute__((packed)); ++++++ ++++++/* Stream Allocate Reply */ ++++++struct sst_hsw_ipc_stream_alloc_reply { ++++++ u32 stream_hw_id; ++++++ u32 mixer_hw_id; // returns rate ???? ++++++ u32 read_position_register_address; ++++++ u32 presentation_position_register_address; ++++++ u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; ++++++ u32 volume_register_address[SST_HSW_NO_CHANNELS]; ++++++} __attribute__((packed)); ++++++ ++++++/* Get Mixer Stream Info */ ++++++struct sst_hsw_ipc_stream_info_reply { ++++++ u32 mixer_hw_id; ++++++ u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; ++++++ u32 volume_register_address[SST_HSW_NO_CHANNELS]; ++++++} __attribute__((packed)); ++++++ ++++++/* DX State Request */ ++++++struct sst_hsw_ipc_dx_req { ++++++ u8 state; ++++++ u8 reserved[3]; ++++++} __attribute__((packed)); ++++++ ++++++/* DX State Reply Memory Info Item */ ++++++struct sst_hsw_ipc_dx_memory_item { ++++++ u32 offset; ++++++ u32 size; ++++++ u32 source; ++++++} __attribute__((packed)); ++++++ ++++++/* DX State Reply */ ++++++struct sst_hsw_ipc_dx_reply { ++++++ u32 entries_no; ++++++ struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS]; ++++++} __attribute__((packed)); ++++++ ++++++struct sst_hsw_ipc_fw_version; ++++++ ++++++/* SST Init & Free */ ++++++struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length, ++++++ u32 fw_offset); ++++++void sst_hsw_free(struct sst_hsw *hsw); ++++++int sst_hsw_fw_get_version(struct sst_hsw *hsw, ++++++ struct sst_hsw_ipc_fw_version *version); ++++++u32 create_channel_map(enum sst_hsw_channel_config config); ++++++ ++++++/* Stream Mixer Controls - */ ++++++int sst_hsw_stream_mute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 stage_id, u32 channel); ++++++int sst_hsw_stream_unmute(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 stage_id, u32 channel); ++++++ ++++++int sst_hsw_stream_set_volume(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume); ++++++int sst_hsw_stream_get_volume(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume); ++++++ ++++++int sst_hsw_stream_set_volume_curve(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u64 curve_duration, ++++++ enum sst_hsw_volume_curve curve); ++++++ ++++++/* Global Mixer Controls - */ ++++++int sst_hsw_mixer_mute(struct sst_hsw *hsw, u32 stage_id, u32 channel); ++++++int sst_hsw_mixer_unmute(struct sst_hsw *hsw, u32 stage_id, u32 channel); ++++++ ++++++int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ++++++ u32 volume); ++++++int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, ++++++ u32 *volume); ++++++ ++++++int sst_hsw_mixer_set_volume_curve(struct sst_hsw *hsw, ++++++ u64 curve_duration, enum sst_hsw_volume_curve curve); ++++++ ++++++/* Stream API */ ++++++struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, ++++++ u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data), ++++++ void *data); ++++++ ++++++int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream); ++++++ ++++++/* Stream Configuration */ ++++++int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ enum sst_hsw_stream_path_id path_id, ++++++ enum sst_hsw_stream_type stream_type, ++++++ enum sst_hsw_stream_format format_id); ++++++ ++++++int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 ring_pt_address, u32 num_pages, ++++++ u32 ring_size, u32 ring_offset, u32 ring_first_pfn); ++++++ ++++++int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream); ++++++ ++++++int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ u32 bits); ++++++int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ int rate); ++++++int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ enum sst_hsw_bitdepth bits); ++++++int sst_hsw_stream_set_channels(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, int channels); ++++++int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 map, ++++++ enum sst_hsw_channel_config config); ++++++int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ enum sst_hsw_interleaving style); ++++++int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, ++++++ u32 entry_point); ++++++int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 offset, u32 size); ++++++int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 offset, u32 size); ++++++int sst_hsw_stream_get_hw_id(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream); ++++++int sst_hsw_stream_get_mixer_id(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream); ++++++u32 sst_hsw_stream_get_read_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream); ++++++u32 sst_hsw_stream_get_pointer_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream); ++++++u32 sst_hsw_stream_get_peak_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 channel); ++++++u32 sst_hsw_stream_get_vol_reg(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 channel); ++++++int sst_hsw_mixer_get_info(struct sst_hsw *hsw); ++++++ ++++++/* Stream ALSA trigger operations */ ++++++int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ int wait); ++++++int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, ++++++ int wait); ++++++int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream); ++++++ ++++++/* Stream pointer positions */ ++++++int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 *position); ++++++int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 *position); ++++++int sst_hsw_stream_set_write_position(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream, u32 stage_id, u32 position); ++++++int sst_hsw_get_dsp_position(struct sst_hsw *hsw, ++++++ struct sst_hsw_stream *stream); ++++++ ++++++/* HW port config */ ++++++int sst_hsw_device_set_config(struct sst_hsw *hsw, ++++++ enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, ++++++ enum sst_hsw_device_mode mode, u32 clock_divider); ++++++ ++++++/* DX Config */ ++++++int sst_hsw_dx_set_state(struct sst_hsw *hsw, ++++++ enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx); ++++++int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, ++++++ u32 *offset, u32 *size, u32 *source); ++++++ ++++++/* init */ ++++++int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); ++++++void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); ++++++struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); ++++++void sst_hsw_set_scratch_module(struct sst_hsw *hsw, ++++++ struct sst_module *scratch); ++++++ ++++++#endif diff --cc sound/soc/intel/sst-haswell-pcm.c index 000000000000,000000000000,000000000000,000000000000,000000000000,000000000000..0a32dd13a23d new file mode 100644 --- /dev/null +++ b/sound/soc/intel/sst-haswell-pcm.c @@@@@@@ -1,0 -1,0 -1,0 -1,0 -1,0 -1,0 +1,872 @@@@@@@ ++++++/* ++++++ * Intel SST Haswell/Broadwell PCM Support ++++++ * ++++++ * Copyright (C) 2013, Intel Corporation. All rights reserved. ++++++ * ++++++ * This program is free software; you can redistribute it and/or ++++++ * modify it under the terms of the GNU General Public License version ++++++ * 2 as published by the Free Software Foundation. ++++++ * ++++++ * This program is distributed in the hope that it will be useful, ++++++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++++++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++++++ * GNU General Public License for more details. ++++++ * ++++++ */ ++++++ ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++#include ++++++ ++++++#include "sst-haswell-ipc.h" ++++++#include "sst-dsp-priv.h" ++++++#include "sst-dsp.h" ++++++ ++++++#define HSW_PCM_COUNT 6 ++++++#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */ ++++++ ++++++/* simple volume table */ ++++++static const u32 volume_map[] = { ++++++ HSW_VOLUME_MAX >> 30, ++++++ HSW_VOLUME_MAX >> 29, ++++++ HSW_VOLUME_MAX >> 28, ++++++ HSW_VOLUME_MAX >> 27, ++++++ HSW_VOLUME_MAX >> 26, ++++++ HSW_VOLUME_MAX >> 25, ++++++ HSW_VOLUME_MAX >> 24, ++++++ HSW_VOLUME_MAX >> 23, ++++++ HSW_VOLUME_MAX >> 22, ++++++ HSW_VOLUME_MAX >> 21, ++++++ HSW_VOLUME_MAX >> 20, ++++++ HSW_VOLUME_MAX >> 19, ++++++ HSW_VOLUME_MAX >> 18, ++++++ HSW_VOLUME_MAX >> 17, ++++++ HSW_VOLUME_MAX >> 16, ++++++ HSW_VOLUME_MAX >> 15, ++++++ HSW_VOLUME_MAX >> 14, ++++++ HSW_VOLUME_MAX >> 13, ++++++ HSW_VOLUME_MAX >> 12, ++++++ HSW_VOLUME_MAX >> 11, ++++++ HSW_VOLUME_MAX >> 10, ++++++ HSW_VOLUME_MAX >> 9, ++++++ HSW_VOLUME_MAX >> 8, ++++++ HSW_VOLUME_MAX >> 7, ++++++ HSW_VOLUME_MAX >> 6, ++++++ HSW_VOLUME_MAX >> 5, ++++++ HSW_VOLUME_MAX >> 4, ++++++ HSW_VOLUME_MAX >> 3, ++++++ HSW_VOLUME_MAX >> 2, ++++++ HSW_VOLUME_MAX >> 1, ++++++ HSW_VOLUME_MAX >> 0, ++++++}; ++++++ ++++++#define HSW_PCM_PERIODS_MAX 64 ++++++#define HSW_PCM_PERIODS_MIN 2 ++++++ ++++++static const struct snd_pcm_hardware hsw_pcm_hardware = { ++++++ .info = SNDRV_PCM_INFO_MMAP | ++++++ SNDRV_PCM_INFO_MMAP_VALID | ++++++ SNDRV_PCM_INFO_INTERLEAVED | ++++++ SNDRV_PCM_INFO_PAUSE | ++++++ SNDRV_PCM_INFO_RESUME | ++++++ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, ++++++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE | ++++++ SNDRV_PCM_FMTBIT_S32_LE, ++++++ .period_bytes_min = PAGE_SIZE, ++++++ .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, ++++++ .periods_min = HSW_PCM_PERIODS_MIN, ++++++ .periods_max = HSW_PCM_PERIODS_MAX, ++++++ .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, ++++++}; ++++++ ++++++/* private data for each PCM DSP stream */ ++++++struct hsw_pcm_data { ++++++ int dai_id; ++++++ struct sst_hsw_stream *stream; ++++++ u32 volume[2]; ++++++ struct snd_pcm_substream *substream; ++++++ struct snd_compr_stream *cstream; ++++++ unsigned int wpos; ++++++ struct mutex mutex; ++++++}; ++++++ ++++++/* private data for the driver */ ++++++struct hsw_priv_data { ++++++ /* runtime DSP */ ++++++ struct sst_hsw *hsw; ++++++ ++++++ /* page tables */ ++++++ unsigned char *pcm_pg[HSW_PCM_COUNT][2]; ++++++ ++++++ /* DAI data */ ++++++ struct hsw_pcm_data pcm[HSW_PCM_COUNT]; ++++++}; ++++++ ++++++static inline u32 hsw_mixer_to_ipc(unsigned int value) ++++++{ ++++++ if (value >= ARRAY_SIZE(volume_map)) ++++++ return volume_map[0]; ++++++ else ++++++ return volume_map[value]; ++++++} ++++++ ++++++static inline unsigned int hsw_ipc_to_mixer(u32 value) ++++++{ ++++++ int i; ++++++ ++++++ for (i = 0; i < ARRAY_SIZE(volume_map); i++) { ++++++ if (volume_map[i] >= value) ++++++ return i; ++++++ } ++++++ ++++++ return i - 1; ++++++} ++++++ ++++++static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, ++++++ struct snd_ctl_elem_value *ucontrol) ++++++{ ++++++ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol); ++++++ struct soc_mixer_control *mc = ++++++ (struct soc_mixer_control *)kcontrol->private_value; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(platform); ++++++ struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ u32 volume; ++++++ ++++++ mutex_lock(&pcm_data->mutex); ++++++ ++++++ if (!pcm_data->stream) { ++++++ pcm_data->volume[0] = ++++++ hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); ++++++ pcm_data->volume[1] = ++++++ hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return 0; ++++++ } ++++++ ++++++ if (ucontrol->value.integer.value[0] == ++++++ ucontrol->value.integer.value[1]) { ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); ++++++ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 2, volume); ++++++ } else { ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); ++++++ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); ++++++ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); ++++++ } ++++++ ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return 0; ++++++} ++++++ ++++++static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, ++++++ struct snd_ctl_elem_value *ucontrol) ++++++{ ++++++ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol); ++++++ struct soc_mixer_control *mc = ++++++ (struct soc_mixer_control *)kcontrol->private_value; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(platform); ++++++ struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ u32 volume; ++++++ ++++++ mutex_lock(&pcm_data->mutex); ++++++ ++++++ if (!pcm_data->stream) { ++++++ ucontrol->value.integer.value[0] = ++++++ hsw_ipc_to_mixer(pcm_data->volume[0]); ++++++ ucontrol->value.integer.value[1] = ++++++ hsw_ipc_to_mixer(pcm_data->volume[1]); ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return 0; ++++++ } ++++++ ++++++ sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); ++++++ ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); ++++++ sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); ++++++ ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); ++++++ mutex_unlock(&pcm_data->mutex); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int hsw_volume_put(struct snd_kcontrol *kcontrol, ++++++ struct snd_ctl_elem_value *ucontrol) ++++++{ ++++++ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol); ++++++ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ u32 volume; ++++++ ++++++ if (ucontrol->value.integer.value[0] == ++++++ ucontrol->value.integer.value[1]) { ++++++ ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); ++++++ sst_hsw_mixer_set_volume(hsw, 0, 2, volume); ++++++ ++++++ } else { ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); ++++++ sst_hsw_mixer_set_volume(hsw, 0, 0, volume); ++++++ ++++++ volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); ++++++ sst_hsw_mixer_set_volume(hsw, 0, 1, volume); ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int hsw_volume_get(struct snd_kcontrol *kcontrol, ++++++ struct snd_ctl_elem_value *ucontrol) ++++++{ ++++++ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol); ++++++ struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ unsigned int volume = 0; ++++++ ++++++ sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); ++++++ ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); ++++++ ++++++ sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); ++++++ ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* TLV used by both global and stream volumes */ ++++++static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); ++++++ ++++++/* System Pin has no volume control */ ++++++static const struct snd_kcontrol_new hsw_volume_controls[] = { ++++++ /* Global DSP volume */ ++++++ SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, ++++++ ARRAY_SIZE(volume_map) -1, 0, ++++++ hsw_volume_get, hsw_volume_put, hsw_vol_tlv), ++++++ /* Offload 0 volume */ ++++++ SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, ++++++ ARRAY_SIZE(volume_map), 0, ++++++ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), ++++++ /* Offload 1 volume */ ++++++ SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, ++++++ ARRAY_SIZE(volume_map), 0, ++++++ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), ++++++ /* Loopback volume */ ++++++ SOC_DOUBLE_EXT_TLV("Loopback Capture Volume", 3, 0, 8, ++++++ ARRAY_SIZE(volume_map), 0, ++++++ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), ++++++ /* Mic Capture volume */ ++++++ SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, ++++++ ARRAY_SIZE(volume_map), 0, ++++++ hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), ++++++}; ++++++ ++++++/* Create DMA buffer page table for DSP */ ++++++static int create_adsp_page_table(struct hsw_priv_data *pdata, ++++++ struct snd_soc_pcm_runtime *rtd, ++++++ unsigned char *dma_area, size_t size, int pcm, int stream) ++++++{ ++++++ int i, pages; ++++++ ++++++ if (size % PAGE_SIZE) ++++++ pages = (size / PAGE_SIZE) + 1; ++++++ else ++++++ pages = size / PAGE_SIZE; ++++++ ++++++ dev_dbg(rtd->dev, "generating page table for %p size 0x%zu pages %d\n", ++++++ dma_area, size, pages); ++++++ ++++++ for (i = 0; i < pages; i++) { ++++++ u32 idx = (((i << 2) + i)) >> 1; ++++++ u32 pfn = (virt_to_phys(dma_area + i * PAGE_SIZE)) >> PAGE_SHIFT; ++++++ u32 *pg_table; ++++++ ++++++ dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); ++++++ ++++++ pg_table = (u32*)(pdata->pcm_pg[pcm][stream] + idx); ++++++ ++++++ if (i & 1) ++++++ *pg_table |= (pfn << 4); ++++++ else ++++++ *pg_table |= pfn; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++/* this may get called several times by oss emulation */ ++++++static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, ++++++ struct snd_pcm_hw_params *params) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct snd_pcm_runtime *runtime = substream->runtime; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ struct sst_module *module_data; ++++++ struct sst_dsp *dsp; ++++++ enum sst_hsw_stream_type stream_type; ++++++ enum sst_hsw_stream_path_id path_id; ++++++ u32 rate, bits, map, pages, module_id; ++++++ u8 channels; ++++++ int ret; ++++++ ++++++ /* stream direction */ ++++++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++++++ path_id = SST_HSW_STREAM_PATH_SSP0_OUT; ++++++ else ++++++ path_id = SST_HSW_STREAM_PATH_SSP0_IN; ++++++ ++++++ /* DSP stream type depends on DAI ID */ ++++++ switch (rtd->cpu_dai->id) { ++++++ case 0: ++++++ stream_type = SST_HSW_STREAM_TYPE_SYSTEM; ++++++ module_id = SST_HSW_MODULE_PCM_SYSTEM; ++++++ break; ++++++ case 1: ++++++ case 2: ++++++ stream_type = SST_HSW_STREAM_TYPE_RENDER; ++++++ module_id = SST_HSW_MODULE_PCM; ++++++ break; ++++++ case 3: ++++++ /* path ID needs to be OUT for loopback */ ++++++ stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; ++++++ path_id = SST_HSW_STREAM_PATH_SSP0_OUT; ++++++ module_id = SST_HSW_MODULE_PCM_REFERENCE; ++++++ break; ++++++ case 4: ++++++ stream_type = SST_HSW_STREAM_TYPE_CAPTURE; ++++++ module_id = SST_HSW_MODULE_PCM_CAPTURE; ++++++ break; ++++++ default: ++++++ dev_err(rtd->dev, "error: invalid DAI ID %d\n", ++++++ rtd->cpu_dai->id); ++++++ return -EINVAL; ++++++ }; ++++++ ++++++ ret = sst_hsw_stream_format(hsw, pcm_data->stream, ++++++ path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: failed to set format %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ rate = params_rate(params); ++++++ ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: could not set rate %d\n", rate); ++++++ return ret; ++++++ } ++++++ ++++++ switch (params_format(params)) { ++++++ case SNDRV_PCM_FORMAT_S16_LE: ++++++ bits = SST_HSW_DEPTH_16BIT; ++++++ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); ++++++ break; ++++++ case SNDRV_PCM_FORMAT_S24_LE: ++++++ bits = SST_HSW_DEPTH_24BIT; ++++++ sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); ++++++ break; ++++++ default: ++++++ dev_err(rtd->dev, "error: invalid format %d\n", ++++++ params_format(params)); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: could not set bits %d\n", bits); ++++++ return ret; ++++++ } ++++++ ++++++ /* we only support stereo atm */ ++++++ channels = params_channels(params); ++++++ if (channels != 2) { ++++++ dev_err(rtd->dev, "error: invalid channels %d\n", channels); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); ++++++ sst_hsw_stream_set_map_config(hsw, pcm_data->stream, ++++++ map, SST_HSW_CHANNEL_CONFIG_STEREO); ++++++ ++++++ ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: could not set channels %d\n", ++++++ channels); ++++++ return ret; ++++++ } ++++++ ++++++ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", ++++++ params_buffer_bytes(params), ret); ++++++ return ret; ++++++ } ++++++ ++++++ ret = create_adsp_page_table(pdata, rtd, runtime->dma_area, ++++++ runtime->dma_bytes, rtd->cpu_dai->id, substream->stream); ++++++ if (ret < 0) ++++++ return ret; ++++++ ++++++ sst_hsw_stream_set_style(hsw, pcm_data->stream, ++++++ SST_HSW_INTERLEAVING_PER_CHANNEL); ++++++ ++++++ if (runtime->dma_bytes % PAGE_SIZE) ++++++ pages = (runtime->dma_bytes / PAGE_SIZE) + 1; ++++++ else ++++++ pages = runtime->dma_bytes / PAGE_SIZE; ++++++ ++++++ ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, ++++++ virt_to_phys(pdata->pcm_pg[rtd->cpu_dai->id][substream->stream]), ++++++ pages, runtime->dma_bytes, 0, ++++++ (u32)(virt_to_phys(runtime->dma_area) >> PAGE_SHIFT)); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ dsp = sst_hsw_get_dsp(hsw); ++++++ ++++++ module_data = sst_module_get_from_id(dsp, module_id); ++++++ if (module_data == NULL) { ++++++ dev_err(rtd->dev, "error: failed to get module config\n"); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ /* we use hardcoded memory offsets atm, will be updated for new FW */ ++++++ if (stream_type == SST_HSW_STREAM_TYPE_CAPTURE) { ++++++ sst_hsw_stream_set_module_info(hsw, pcm_data->stream, ++++++ SST_HSW_MODULE_PCM_CAPTURE, module_data->entry); ++++++ sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, ++++++ 0x449400, 0x4000); ++++++ sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, ++++++ 0x400000, 0); ++++++ } else { /* stream_type == SST_HSW_STREAM_TYPE_SYSTEM */ ++++++ sst_hsw_stream_set_module_info(hsw, pcm_data->stream, ++++++ SST_HSW_MODULE_PCM_SYSTEM, module_data->entry); ++++++ ++++++ sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, ++++++ module_data->offset, module_data->size); ++++++ sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, ++++++ 0x44d400, 0x3800); ++++++ ++++++ sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, ++++++ module_data->offset, module_data->size); ++++++ sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, ++++++ 0x400000, 0); ++++++ } ++++++ ++++++ ret = sst_hsw_stream_commit(hsw, pcm_data->stream); ++++++ if (ret < 0) { ++++++ dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); ++++++ return ret; ++++++ } ++++++ ++++++ ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); ++++++ if (ret < 0) ++++++ dev_err(rtd->dev, "error: failed to pause %d\n", ret); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static int hsw_pcm_hw_free(struct snd_pcm_substream *substream) ++++++{ ++++++ snd_pcm_lib_free_pages(substream); ++++++ return 0; ++++++} ++++++ ++++++static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ ++++++ switch (cmd) { ++++++ case SNDRV_PCM_TRIGGER_START: ++++++ case SNDRV_PCM_TRIGGER_RESUME: ++++++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++++++ sst_hsw_stream_resume(hsw, pcm_data->stream, 0); ++++++ break; ++++++ case SNDRV_PCM_TRIGGER_STOP: ++++++ case SNDRV_PCM_TRIGGER_SUSPEND: ++++++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++++++ sst_hsw_stream_pause(hsw, pcm_data->stream, 0); ++++++ break; ++++++ default: ++++++ break; ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) ++++++{ ++++++ struct hsw_pcm_data *pcm_data = data; ++++++ struct snd_pcm_substream *substream = pcm_data->substream; ++++++ struct snd_pcm_runtime *runtime = substream->runtime; ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ u32 pos; ++++++ ++++++ pos = frames_to_bytes(runtime, ++++++ (runtime->control->appl_ptr % runtime->buffer_size)); ++++++ ++++++ dev_dbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); ++++++ ++++++ /* let alsa know we have play a period */ ++++++ snd_pcm_period_elapsed(substream); ++++++ return pos; ++++++} ++++++ ++++++static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct snd_pcm_runtime *runtime = substream->runtime; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ snd_pcm_uframes_t offset; ++++++ ++++++ offset = bytes_to_frames(runtime, ++++++ sst_hsw_get_dsp_position(hsw, pcm_data->stream)); ++++++ ++++++ dev_dbg(rtd->dev, "PCM: DMA pointer %zu bytes\n", ++++++ frames_to_bytes(runtime, (u32)offset)); ++++++ return offset; ++++++} ++++++ ++++++static int hsw_pcm_open(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct hsw_pcm_data *pcm_data; ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ ++++++ pcm_data = &pdata->pcm[rtd->cpu_dai->id]; ++++++ ++++++ mutex_lock(&pcm_data->mutex); ++++++ ++++++ snd_soc_pcm_set_drvdata(rtd, pcm_data); ++++++ pcm_data->substream = substream; ++++++ ++++++ snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); ++++++ ++++++ pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, ++++++ hsw_notify_pointer, pcm_data); ++++++ if (pcm_data->stream == NULL) { ++++++ dev_err(rtd->dev, "error: failed to create stream\n"); ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return -EINVAL; ++++++ } ++++++ ++++++ /* Set previous saved volume */ ++++++ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, ++++++ 0, pcm_data->volume[0]); ++++++ sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, ++++++ 1, pcm_data->volume[1]); ++++++ ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return 0; ++++++} ++++++ ++++++static int hsw_pcm_close(struct snd_pcm_substream *substream) ++++++{ ++++++ struct snd_soc_pcm_runtime *rtd = substream->private_data; ++++++ struct hsw_priv_data *pdata = ++++++ snd_soc_platform_get_drvdata(rtd->platform); ++++++ struct hsw_pcm_data *pcm_data = snd_soc_pcm_get_drvdata(rtd); ++++++ struct sst_hsw *hsw = pdata->hsw; ++++++ int ret; ++++++ ++++++ mutex_lock(&pcm_data->mutex); ++++++ ret = sst_hsw_stream_reset(hsw, pcm_data->stream); ++++++ if (ret < 0) { ++++++ dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); ++++++ goto out; ++++++ } ++++++ ++++++ ret = sst_hsw_stream_free(hsw, pcm_data->stream); ++++++ if (ret < 0) { ++++++ dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); ++++++ goto out; ++++++ } ++++++ pcm_data->stream = NULL; ++++++ ++++++out: ++++++ mutex_unlock(&pcm_data->mutex); ++++++ return ret; ++++++} ++++++ ++++++static struct snd_pcm_ops hsw_pcm_ops = { ++++++ .open = hsw_pcm_open, ++++++ .close = hsw_pcm_close, ++++++ .ioctl = snd_pcm_lib_ioctl, ++++++ .hw_params = hsw_pcm_hw_params, ++++++ .hw_free = hsw_pcm_hw_free, ++++++ .trigger = hsw_pcm_trigger, ++++++ .pointer = hsw_pcm_pointer, ++++++ .mmap = snd_pcm_lib_default_mmap, ++++++}; ++++++ ++++++static void hsw_pcm_free(struct snd_pcm *pcm) ++++++{ ++++++ snd_pcm_lib_preallocate_free_for_all(pcm); ++++++} ++++++ ++++++static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) ++++++{ ++++++ struct snd_pcm *pcm = rtd->pcm; ++++++ int ret = 0; ++++++ ++++++ ret = dma_coerce_mask_and_coherent(rtd->card->dev, DMA_BIT_MASK(32)); ++++++ if (ret) ++++++ return ret; ++++++ ++++++ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || ++++++ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { ++++++ ret = snd_pcm_lib_preallocate_pages_for_all(pcm, ++++++ SNDRV_DMA_TYPE_DEV, ++++++ rtd->card->dev, ++++++ hsw_pcm_hardware.buffer_bytes_max, ++++++ hsw_pcm_hardware.buffer_bytes_max); ++++++ if (ret) { ++++++ dev_err(rtd->dev, "dma buffer allocation failed %d\n", ++++++ ret); ++++++ return ret; ++++++ } ++++++ } ++++++ ++++++ return ret; ++++++} ++++++ ++++++#define HSW_FORMATS \ ++++++ (SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE |\ ++++++ SNDRV_PCM_FMTBIT_S32_LE) ++++++ ++++++static struct snd_soc_dai_driver hsw_dais[] = { ++++++ { ++++++ .name = "System Pin", ++++++ .playback = { ++++++ .stream_name = "System Playback", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_48000, ++++++ .formats = SNDRV_PCM_FMTBIT_S16_LE, ++++++ }, ++++++ }, ++++++ { ++++++ /* PCM */ ++++++ .name = "Offload0 Pin", ++++++ .playback = { ++++++ .stream_name = "Offload0 Playback", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_8000_192000, ++++++ .formats = HSW_FORMATS, ++++++ }, ++++++ }, ++++++ { ++++++ /* PCM */ ++++++ .name = "Offload1 Pin", ++++++ .playback = { ++++++ .stream_name = "Offload1 Playback", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_8000_192000, ++++++ .formats = HSW_FORMATS, ++++++ }, ++++++ }, ++++++ { ++++++ .name = "Loopback Pin", ++++++ .capture = { ++++++ .stream_name = "Loopback Capture", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_8000_192000, ++++++ .formats = HSW_FORMATS, ++++++ }, ++++++ }, ++++++ { ++++++ .name = "Capture Pin", ++++++ .capture = { ++++++ .stream_name = "Analog Capture", ++++++ .channels_min = 2, ++++++ .channels_max = 2, ++++++ .rates = SNDRV_PCM_RATE_8000_192000, ++++++ .formats = HSW_FORMATS, ++++++ }, ++++++ }, ++++++}; ++++++ ++++++static const struct snd_soc_dapm_widget widgets[] = { ++++++ ++++++ /* Backend DAIs */ ++++++ SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), ++++++ SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), ++++++ SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), ++++++ SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), ++++++ ++++++ /* Global Playback Mixer */ ++++++ SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), ++++++}; ++++++ ++++++static const struct snd_soc_dapm_route graph[] = { ++++++ ++++++ /* Playback Mixer */ ++++++ {"Playback VMixer", NULL, "System Playback"}, ++++++ {"Playback VMixer", NULL, "Offload0 Playback"}, ++++++ {"Playback VMixer", NULL, "Offload1 Playback"}, ++++++ ++++++ {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, ++++++ ++++++ {"Analog Capture", NULL, "SSP0 CODEC IN"}, ++++++}; ++++++ ++++++static int hsw_pcm_probe(struct snd_soc_platform *platform) ++++++{ ++++++ struct sst_pdata *pdata = dev_get_platdata(platform->dev); ++++++ struct hsw_priv_data *priv_data; ++++++ int i; ++++++ ++++++ if (!pdata) ++++++ return -ENODEV; ++++++ ++++++ priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), GFP_KERNEL); ++++++ priv_data->hsw = pdata->dsp; ++++++ snd_soc_platform_set_drvdata(platform, priv_data); ++++++ ++++++ /* allocate DSP buffer page tables */ ++++++ for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { ++++++ ++++++ mutex_init(&priv_data->pcm[i].mutex); ++++++ ++++++ /* playback */ ++++++ if (hsw_dais[i].playback.channels_min) { ++++++ priv_data->pcm_pg[i][0] = kzalloc(PAGE_SIZE, GFP_DMA); ++++++ if (priv_data->pcm_pg[i][0] == NULL) ++++++ goto err; ++++++ } ++++++ ++++++ /* capture */ ++++++ if (hsw_dais[i].capture.channels_min) { ++++++ priv_data->pcm_pg[i][1] = kzalloc(PAGE_SIZE, GFP_DMA); ++++++ if (priv_data->pcm_pg[i][1] == NULL) ++++++ goto err; ++++++ } ++++++ } ++++++ ++++++ return 0; ++++++ ++++++err: ++++++ for (;i >= 0; i--) { ++++++ if (hsw_dais[i].playback.channels_min) ++++++ kfree(priv_data->pcm_pg[i][0]); ++++++ if (hsw_dais[i].capture.channels_min) ++++++ kfree(priv_data->pcm_pg[i][1]); ++++++ } ++++++ return -ENOMEM; ++++++} ++++++ ++++++static int hsw_pcm_remove(struct snd_soc_platform *platform) ++++++{ ++++++ struct hsw_priv_data *priv_data = ++++++ snd_soc_platform_get_drvdata(platform); ++++++ int i; ++++++ ++++++ for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { ++++++ if (hsw_dais[i].playback.channels_min) ++++++ kfree(priv_data->pcm_pg[i][0]); ++++++ if (hsw_dais[i].capture.channels_min) ++++++ kfree(priv_data->pcm_pg[i][1]); ++++++ } ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct snd_soc_platform_driver hsw_soc_platform = { ++++++ .probe = hsw_pcm_probe, ++++++ .remove = hsw_pcm_remove, ++++++ .ops = &hsw_pcm_ops, ++++++ .pcm_new = hsw_pcm_new, ++++++ .pcm_free = hsw_pcm_free, ++++++ .controls = hsw_volume_controls, ++++++ .num_controls = ARRAY_SIZE(hsw_volume_controls), ++++++ .dapm_widgets = widgets, ++++++ .num_dapm_widgets = ARRAY_SIZE(widgets), ++++++ .dapm_routes = graph, ++++++ .num_dapm_routes = ARRAY_SIZE(graph), ++++++}; ++++++ ++++++static const struct snd_soc_component_driver hsw_dai_component = { ++++++ .name = "haswell-dai", ++++++}; ++++++ ++++++static int hsw_pcm_dev_probe(struct platform_device *pdev) ++++++{ ++++++ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); ++++++ int ret; ++++++ ++++++ ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); ++++++ if (ret < 0) ++++++ return -ENODEV; ++++++ ++++++ ret = snd_soc_register_platform(&pdev->dev, &hsw_soc_platform); ++++++ if (ret < 0) ++++++ goto err_plat; ++++++ ++++++ ret = snd_soc_register_component(&pdev->dev, &hsw_dai_component, ++++++ hsw_dais, ARRAY_SIZE(hsw_dais)); ++++++ if (ret < 0) ++++++ goto err_comp; ++++++ ++++++ return 0; ++++++ ++++++err_comp: ++++++ snd_soc_unregister_platform(&pdev->dev); ++++++err_plat: ++++++ sst_hsw_dsp_free(&pdev->dev, sst_pdata); ++++++ return 0; ++++++} ++++++ ++++++static int hsw_pcm_dev_remove(struct platform_device *pdev) ++++++{ ++++++ struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); ++++++ ++++++ snd_soc_unregister_platform(&pdev->dev); ++++++ snd_soc_unregister_component(&pdev->dev); ++++++ sst_hsw_dsp_free(&pdev->dev, sst_pdata); ++++++ ++++++ return 0; ++++++} ++++++ ++++++static struct platform_driver hsw_pcm_driver = { ++++++ .driver = { ++++++ .name = "haswell-pcm-audio", ++++++ .owner = THIS_MODULE, ++++++ }, ++++++ ++++++ .probe = hsw_pcm_dev_probe, ++++++ .remove = hsw_pcm_dev_remove, ++++++}; ++++++module_platform_driver(hsw_pcm_driver); ++++++ ++++++MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); ++++++MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); ++++++MODULE_LICENSE("GPL v2"); ++++++MODULE_ALIAS("platform:haswell-pcm-audio"); diff --cc sound/soc/intel/sst-mfld-dsp.h index 0fce1de284ff,0fce1de284ff,0fce1de284ff,0fce1de284ff,0fce1de284ff,0fce1de284ff..3b63edc04b7f --- a/sound/soc/intel/sst-mfld-dsp.h +++ b/sound/soc/intel/sst-mfld-dsp.h @@@@@@@ -1,7 -1,7 -1,7 -1,7 -1,7 -1,7 +1,7 @@@@@@@ ------#ifndef __SST_DSP_H__ ------#define __SST_DSP_H__ ++++++#ifndef __SST_MFLD_DSP_H__ ++++++#define __SST_MFLD_DSP_H__ /* ------ * sst_dsp.h - Intel SST Driver for audio engine ++++++ * sst_mfld_dsp.h - Intel SST Driver for audio engine * * Copyright (C) 2008-12 Intel Corporation * Authors: Vinod Koul @@@@@@@ -131,4 -131,4 -131,4 -131,4 -131,4 -131,4 +131,4 @@@@@@@ struct snd_sst_params struct snd_sst_alloc_params_ext aparams; }; ------#endif /* __SST_DSP_H__ */ ++++++#endif /* __SST_MFLD_DSP_H__ */ diff --cc sound/soc/intel/sst-mfld-platform.c index f465a8180863,f465a8180863,f465a8180863,f465a8180863,f465a8180863,f465a8180863..840306c2ef14 --- a/sound/soc/intel/sst-mfld-platform.c +++ b/sound/soc/intel/sst-mfld-platform.c @@@@@@@ -1,5 -1,5 -1,5 -1,5 -1,5 -1,5 +1,5 @@@@@@@ /* ------ * sst_platform.c - Intel MID Platform driver ++++++ * sst_mfld_platform.c - Intel MID Platform driver * * Copyright (C) 2010-2013 Intel Corp * Author: Vinod Koul @@@@@@@ -33,7 -33,7 -33,7 -33,7 -33,7 -33,7 +33,7 @@@@@@@ #include #include #include ------#include "sst_platform.h" ++++++#include "sst-mfld-platform.h" static struct sst_device *sst; static DEFINE_MUTEX(sst_lock); @@@@@@@ -709,7 -709,7 -709,7 -709,7 -709,7 -709,7 +709,7 @@@@@@@ static int sst_platform_remove(struct p static struct platform_driver sst_platform_driver = { .driver = { ------ .name = "sst-platform", ++++++ .name = "sst-mfld-platform", .owner = THIS_MODULE, }, .probe = sst_platform_probe, @@@@@@@ -722,4 -722,4 -722,4 -722,4 -722,4 -722,4 +722,4 @@@@@@@ MODULE_DESCRIPTION("ASoC Intel(R) MID P MODULE_AUTHOR("Vinod Koul "); MODULE_AUTHOR("Harsha Priya "); MODULE_LICENSE("GPL v2"); ------MODULE_ALIAS("platform:sst-platform"); ++++++MODULE_ALIAS("platform:sst-mfld-platform"); diff --cc sound/soc/intel/sst-mfld-platform.h index bee64fb7d2ef,bee64fb7d2ef,bee64fb7d2ef,bee64fb7d2ef,bee64fb7d2ef,bee64fb7d2ef..0c4e2ddcecb1 --- a/sound/soc/intel/sst-mfld-platform.h +++ b/sound/soc/intel/sst-mfld-platform.h @@@@@@@ -1,5 -1,5 -1,5 -1,5 -1,5 -1,5 +1,5 @@@@@@@ /* ------ * sst_platform.h - Intel MID Platform driver header file ++++++ * sst_mfld_platform.h - Intel MID Platform driver header file * * Copyright (C) 2010 Intel Corp * Author: Vinod Koul @@@@@@@ -27,7 -27,7 -27,7 -27,7 -27,7 -27,7 +27,7 @@@@@@@ #ifndef __SST_PLATFORMDRV_H__ #define __SST_PLATFORMDRV_H__ ------#include "sst_dsp.h" ++++++#include "sst-mfld-dsp.h" #define SST_MONO 1 #define SST_STEREO 2 diff --cc sound/soc/soc-core.c index 4ba0959a0d7b,fe1df50805a3,5b7d3ba87c7a,fe1df50805a3,fe1df50805a3,fe1df50805a3..359c2849b364 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@@@@@@ -3892,42 -3882,95 -3912,95 -3882,95 -3882,95 -3882,95 +3922,42 @@@@@@@ static inline char *fmt_multiple_name(s } /** ----- * snd_soc_register_dai - Register a DAI with the ASoC core +++++ * snd_soc_unregister_dai - Unregister DAIs from the ASoC core * ----- * @dai: DAI to register +++++ * @component: The component for which the DAIs should be unregistered */ -----static int snd_soc_register_dai(struct device *dev, ----- struct snd_soc_dai_driver *dai_drv) +++++static void snd_soc_unregister_dais(struct snd_soc_component *component) { ----- struct snd_soc_codec *codec; ----- struct snd_soc_dai *dai; ----- ----- dev_dbg(dev, "ASoC: dai register %s\n", dev_name(dev)); - - dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); - if (dai == NULL) - return -ENOMEM; +++++ struct snd_soc_dai *dai, *_dai; - --- dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); - --- if (dai == NULL) - --- return -ENOMEM; - --- ----- /* create DAI component name */ ----- dai->name = fmt_single_name(dev, &dai->id); ----- if (dai->name == NULL) { +++++ list_for_each_entry_safe(dai, _dai, &component->dai_list, list) { +++++ dev_dbg(component->dev, "ASoC: Unregistered DAI '%s'\n", +++++ dai->name); +++++ list_del(&dai->list); +++++ kfree(dai->name); kfree(dai); ----- return -ENOMEM; - } - - dai->dev = dev; - dai->driver = dai_drv; - dai->dapm.dev = dev; - if (!dai->driver->ops) - dai->driver->ops = &null_dai_ops; - - mutex_lock(&client_mutex); - - list_for_each_entry(codec, &codec_list, list) { - if (codec->dev == dev) { - dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\n", - dai->name, codec->name); - dai->codec = codec; - break; - } - } - - if (!dai->codec) - dai->dapm.idle_bias_off = 1; - - list_add(&dai->list, &dai_list); - - mutex_unlock(&client_mutex); - - dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); - - return 0; - } - - /** - * snd_soc_unregister_dai - Unregister a DAI from the ASoC core - * - * @dai: DAI to unregister - */ - static void snd_soc_unregister_dai(struct device *dev) - { - struct snd_soc_dai *dai; - - list_for_each_entry(dai, &dai_list, list) { - if (dev == dai->dev) - goto found; } - --- - --- dai->dev = dev; - --- dai->driver = dai_drv; - --- dai->dapm.dev = dev; - --- if (!dai->driver->ops) - --- dai->driver->ops = &null_dai_ops; - return; ----- - found: ----- mutex_lock(&client_mutex); - --- - --- list_for_each_entry(codec, &codec_list, list) { - --- if (codec->dev == dev) { - --- dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\n", - --- dai->name, codec->name); - --- dai->codec = codec; - --- break; - --- } - --- } - --- - --- if (!dai->codec) - --- dai->dapm.idle_bias_off = 1; - --- - --- list_add(&dai->list, &dai_list); - --- - list_del(&dai->list); ----- mutex_unlock(&client_mutex); - --- - --- dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); ----- - --- return 0; - dev_dbg(dev, "ASoC: Unregistered DAI '%s'\n", dai->name); - kfree(dai->name); - kfree(dai); } /** - --- * snd_soc_unregister_dai - Unregister a DAI from the ASoC core - * snd_soc_register_dais - Register multiple DAIs with the ASoC core +++++ * snd_soc_register_dais - Register a DAI with the ASoC core * - --- * @dai: DAI to unregister - --- */ - ---static void snd_soc_unregister_dai(struct device *dev) - ---{ - --- struct snd_soc_dai *dai; - --- - --- list_for_each_entry(dai, &dai_list, list) { - --- if (dev == dai->dev) - --- goto found; - --- } - --- return; - --- - ---found: - --- mutex_lock(&client_mutex); - --- list_del(&dai->list); - --- mutex_unlock(&client_mutex); - --- - --- dev_dbg(dev, "ASoC: Unregistered DAI '%s'\n", dai->name); - --- kfree(dai->name); - --- kfree(dai); - ---} - --- - ---/** - --- * snd_soc_register_dais - Register multiple DAIs with the ASoC core - --- * ----- * @dai: Array of DAIs to register +++++ * @component: The component the DAIs are registered for +++++ * @codec: The CODEC that the DAIs are registered for, NULL if the component is +++++ * not a CODEC. +++++ * @dai_drv: DAI driver to use for the DAIs * @count: Number of DAIs +++++ * @legacy_dai_naming: Use the legacy naming scheme and let the DAI inherit the +++++ * parent's name. */ -----static int snd_soc_register_dais(struct device *dev, ----- struct snd_soc_dai_driver *dai_drv, size_t count) +++++static int snd_soc_register_dais(struct snd_soc_component *component, +++++ struct snd_soc_codec *codec, struct snd_soc_dai_driver *dai_drv, +++++ size_t count, bool legacy_dai_naming) { ----- struct snd_soc_codec *codec; +++++ struct device *dev = component->dev; struct snd_soc_dai *dai; ----- int i, ret = 0; +++++ unsigned int i; +++++ int ret; dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);