Path: blob/main/sys/arm/broadcom/bcm2835/bcm2835_audio.c
108735 views
/*-1* Copyright (c) 2015 Oleksandr Tymoshenko <[email protected]>2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions and the following disclaimer.8* 2. Redistributions in binary form must reproduce the above copyright9* notice, this list of conditions and the following disclaimer in the10* documentation and/or other materials provided with the distribution.11*12* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND13* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE14* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE15* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE16* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL17* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS18* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)19* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT20* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY21* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF22* SUCH DAMAGE.23*/2425#ifdef HAVE_KERNEL_OPTION_HEADERS26#include "opt_snd.h"27#endif2829#include <dev/sound/pcm/sound.h>3031#include "mixer_if.h"3233#include "interface/compat/vchi_bsd.h"34#include "interface/vchi/vchi.h"35#include "interface/vchiq_arm/vchiq.h"3637#include "vc_vchi_audioserv_defs.h"3839/* Audio destination */40#define DEST_AUTO 041#define DEST_HEADPHONES 142#define DEST_HDMI 24344/* Playback state */45#define PLAYBACK_IDLE 046#define PLAYBACK_PLAYING 147#define PLAYBACK_STOPPING 24849/* Worker thread state */50#define WORKER_RUNNING 051#define WORKER_STOPPING 152#define WORKER_STOPPED 25354/*55* Worker thread flags, set to 1 in flags_pending56* when driver requests one or another operation57* from worker. Cleared to 0 once worker performs58* the operations.59*/60#define AUDIO_PARAMS (1 << 0)61#define AUDIO_PLAY (1 << 1)62#define AUDIO_STOP (1 << 2)6364#define VCHIQ_AUDIO_PACKET_SIZE 400065#define VCHIQ_AUDIO_BUFFER_SIZE 10*VCHIQ_AUDIO_PACKET_SIZE6667#define VCHIQ_AUDIO_MAX_VOLUME68/* volume in terms of 0.01dB */69#define VCHIQ_AUDIO_VOLUME_MIN -1023970#define VCHIQ_AUDIO_VOLUME(db100) (uint32_t)(-((db100) << 8)/100)7172/* dB levels with 5% volume step */73static int db_levels[] = {74VCHIQ_AUDIO_VOLUME_MIN, -4605, -3794, -3218, -2772,75-2407, -2099, -1832, -1597, -1386,76-1195, -1021, -861, -713, -575,77-446, -325, -210, -102, 0,78};7980static uint32_t bcm2835_audio_playfmt[] = {81SND_FORMAT(AFMT_U8, 1, 0),82SND_FORMAT(AFMT_U8, 2, 0),83SND_FORMAT(AFMT_S8, 1, 0),84SND_FORMAT(AFMT_S8, 2, 0),85SND_FORMAT(AFMT_S16_LE, 1, 0),86SND_FORMAT(AFMT_S16_LE, 2, 0),87SND_FORMAT(AFMT_U16_LE, 1, 0),88SND_FORMAT(AFMT_U16_LE, 2, 0),89090};9192static struct pcmchan_caps bcm2835_audio_playcaps = {8000, 48000, bcm2835_audio_playfmt, 0};9394struct bcm2835_audio_info;9596struct bcm2835_audio_chinfo {97struct bcm2835_audio_info *parent;98struct pcm_channel *channel;99struct snd_dbuf *buffer;100uint32_t fmt, spd, blksz;101102/* Pointer to first unsubmitted sample */103uint32_t unsubmittedptr;104/*105* Number of bytes in "submitted but not played"106* pseudo-buffer107*/108int available_space;109int playback_state;110uint64_t callbacks;111uint64_t submitted_samples;112uint64_t retrieved_samples;113uint64_t underruns;114int starved;115struct bcm_log_vars {116unsigned int bsize ;117int slept_for_lack_of_space ;118} log_vars;119#define DEFAULT_LOG_VALUES \120((struct bcm_log_vars) { .bsize = 0 , .slept_for_lack_of_space = 0 })121};122123struct bcm2835_audio_info {124device_t dev;125unsigned int bufsz;126struct bcm2835_audio_chinfo pch;127uint32_t dest, volume;128struct intr_config_hook intr_hook;129130/* VCHI data */131VCHI_INSTANCE_T vchi_instance;132VCHI_CONNECTION_T *vchi_connection;133VCHI_SERVICE_HANDLE_T vchi_handle;134135struct mtx lock;136struct cv worker_cv;137138uint32_t flags_pending;139140int verbose_trace;141/* Worker thread state */142int worker_state;143};144145#define BCM2835_AUDIO_LOCK(sc) mtx_lock(&(sc)->lock)146#define BCM2835_AUDIO_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED)147#define BCM2835_AUDIO_UNLOCK(sc) mtx_unlock(&(sc)->lock)148149#define BCM2835_LOG_ERROR(sc,...) \150do { \151device_printf((sc)->dev, __VA_ARGS__); \152} while(0)153154#define BCM2835_LOG_INFO(sc,...) \155do { \156if (sc->verbose_trace > 0) \157device_printf((sc)->dev, __VA_ARGS__); \158} while(0)159160#define BCM2835_LOG_WARN(sc,...) \161do { \162if (sc->verbose_trace > 1) \163device_printf((sc)->dev, __VA_ARGS__); \164} while(0)165166#define BCM2835_LOG_TRACE(sc,...) \167do { \168if(sc->verbose_trace > 2) \169device_printf((sc)->dev, __VA_ARGS__); \170} while(0)171172/* Useful for circular buffer calcs */173#define MOD_DIFF(front,rear,mod) (((mod) + (front) - (rear)) % (mod))174175176static const char *177dest_description(uint32_t dest)178{179switch (dest) {180case DEST_AUTO:181return "AUTO";182break;183184case DEST_HEADPHONES:185return "HEADPHONES";186break;187188case DEST_HDMI:189return "HDMI";190break;191default:192return "UNKNOWN";193break;194}195}196197static void198bcm2835_worker_update_params(struct bcm2835_audio_info *sc)199{200201BCM2835_AUDIO_LOCKED(sc);202203sc->flags_pending |= AUDIO_PARAMS;204cv_signal(&sc->worker_cv);205}206207static void208bcm2835_worker_play_start(struct bcm2835_audio_info *sc)209{210BCM2835_AUDIO_LOCK(sc);211sc->flags_pending &= ~(AUDIO_STOP);212sc->flags_pending |= AUDIO_PLAY;213cv_signal(&sc->worker_cv);214BCM2835_AUDIO_UNLOCK(sc);215}216217static void218bcm2835_worker_play_stop(struct bcm2835_audio_info *sc)219{220BCM2835_AUDIO_LOCK(sc);221sc->flags_pending &= ~(AUDIO_PLAY);222sc->flags_pending |= AUDIO_STOP;223cv_signal(&sc->worker_cv);224BCM2835_AUDIO_UNLOCK(sc);225}226227static void228bcm2835_audio_callback(void *param, const VCHI_CALLBACK_REASON_T reason, void *msg_handle)229{230struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)param;231int32_t status;232uint32_t msg_len;233VC_AUDIO_MSG_T m;234235if (reason != VCHI_CALLBACK_MSG_AVAILABLE)236return;237238status = vchi_msg_dequeue(sc->vchi_handle,239&m, sizeof m, &msg_len, VCHI_FLAGS_NONE);240if (status != 0)241return;242if (m.type == VC_AUDIO_MSG_TYPE_RESULT) {243if (m.u.result.success) {244device_printf(sc->dev,245"msg type %08x failed\n",246m.type);247}248} else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) {249unsigned int signaled = 0;250struct bcm2835_audio_chinfo *ch ;251#if defined(__aarch64__)252ch = (void *) ((((size_t)m.u.complete.callback) << 32)253| ((size_t)m.u.complete.cookie));254#else255ch = (void *) (m.u.complete.cookie);256#endif257258int count = m.u.complete.count & 0xffff;259int perr = (m.u.complete.count & (1U << 30)) != 0;260261BCM2835_LOG_TRACE(sc, "in:: count:0x%x perr:%d\n",262m.u.complete.count, perr);263264ch->callbacks++;265if (perr)266ch->underruns++;267268BCM2835_AUDIO_LOCK(sc);269if (ch->playback_state != PLAYBACK_IDLE) {270/* Prevent LOR */271BCM2835_AUDIO_UNLOCK(sc);272chn_intr(sc->pch.channel);273BCM2835_AUDIO_LOCK(sc);274}275/* We should check again, state might have changed */276if (ch->playback_state != PLAYBACK_IDLE) {277if (!perr) {278if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) {279device_printf(sc->dev, "inconsistent data in callback:\n");280device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n",281ch->available_space, count, perr);282device_printf(sc->dev,283"retrieved_samples = %ju, submitted_samples = %ju\n",284(uintmax_t)ch->retrieved_samples,285(uintmax_t)ch->submitted_samples);286}287}288ch->available_space += count;289ch->retrieved_samples += count;290/*291* XXXMDC292* Experimental: if VC says it's empty, believe it293* Has to come after the usual adjustments294*/295if(perr){296ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE;297perr = ch->retrieved_samples; // shd be != 0298}299300if ((ch->available_space >= 1*VCHIQ_AUDIO_PACKET_SIZE)){301cv_signal(&sc->worker_cv);302signaled = 1;303}304}305BCM2835_AUDIO_UNLOCK(sc);306if(perr){307BCM2835_LOG_WARN(sc,308"VC starved; reported %u for a total of %u\n"309"worker %s\n", count, perr,310(signaled ? "signaled": "not signaled"));311}312} else313BCM2835_LOG_WARN(sc, "%s: unknown m.type: %d\n", __func__,314m.type);315}316317/* VCHIQ stuff */318static void319bcm2835_audio_init(struct bcm2835_audio_info *sc)320{321int status;322323/* Initialize and create a VCHI connection */324status = vchi_initialise(&sc->vchi_instance);325if (status != 0) {326BCM2835_LOG_ERROR(sc, "vchi_initialise failed: %d\n", status);327return;328}329330status = vchi_connect(NULL, 0, sc->vchi_instance);331if (status != 0) {332BCM2835_LOG_ERROR(sc, "vchi_connect failed: %d\n", status);333return;334}335336SERVICE_CREATION_T params = {337VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER),338VC_AUDIO_SERVER_NAME, /* 4cc service code */339sc->vchi_connection, /* passed in fn pointers */3400, /* rx fifo size */3410, /* tx fifo size */342bcm2835_audio_callback, /* service callback */343sc, /* service callback parameter */3441,3451,3460 /* want crc check on bulk transfers */347};348349status = vchi_service_open(sc->vchi_instance, ¶ms,350&sc->vchi_handle);351352if (status != 0)353sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;354}355356static void357bcm2835_audio_release(struct bcm2835_audio_info *sc)358{359int success;360361if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {362success = vchi_service_close(sc->vchi_handle);363if (success != 0)364BCM2835_LOG_ERROR(sc, "vchi_service_close failed: %d\n",365success);366vchi_service_release(sc->vchi_handle);367sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;368}369370vchi_disconnect(sc->vchi_instance);371}372373static void374bcm2835_audio_reset_channel(struct bcm2835_audio_chinfo *ch)375{376377ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE;378ch->unsubmittedptr = 0;379sndbuf_reset(ch->buffer);380}381382static void383bcm2835_audio_start(struct bcm2835_audio_chinfo *ch)384{385VC_AUDIO_MSG_T m;386int ret;387struct bcm2835_audio_info *sc = ch->parent;388389if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {390m.type = VC_AUDIO_MSG_TYPE_START;391ret = vchi_msg_queue(sc->vchi_handle,392&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);393394if (ret != 0)395BCM2835_LOG_ERROR(sc,396"%s: vchi_msg_queue failed (err %d)\n", __func__,397ret);398}399}400401static void402bcm2835_audio_stop(struct bcm2835_audio_chinfo *ch)403{404VC_AUDIO_MSG_T m;405int ret;406struct bcm2835_audio_info *sc = ch->parent;407408if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {409m.type = VC_AUDIO_MSG_TYPE_STOP;410m.u.stop.draining = 0;411412BCM2835_LOG_INFO(sc,"sending stop\n");413ret = vchi_msg_queue(sc->vchi_handle,414&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);415416if (ret != 0)417BCM2835_LOG_ERROR(sc,418"%s: vchi_msg_queue failed (err %d)\n", __func__,419ret);420}421}422423static void424bcm2835_audio_open(struct bcm2835_audio_info *sc)425{426VC_AUDIO_MSG_T m;427int ret;428429if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {430m.type = VC_AUDIO_MSG_TYPE_OPEN;431ret = vchi_msg_queue(sc->vchi_handle,432&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);433434if (ret != 0)435BCM2835_LOG_ERROR(sc,436"%s: vchi_msg_queue failed (err %d)\n", __func__,437ret);438}439}440441static void442bcm2835_audio_update_controls(struct bcm2835_audio_info *sc, uint32_t volume, uint32_t dest)443{444VC_AUDIO_MSG_T m;445int ret, db;446447if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {448m.type = VC_AUDIO_MSG_TYPE_CONTROL;449m.u.control.dest = dest;450if (volume > 99)451volume = 99;452db = db_levels[volume/5];453m.u.control.volume = VCHIQ_AUDIO_VOLUME(db);454455ret = vchi_msg_queue(sc->vchi_handle,456&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);457458if (ret != 0)459BCM2835_LOG_ERROR(sc,460"%s: vchi_msg_queue failed (err %d)\n", __func__,461ret);462}463}464465static void466bcm2835_audio_update_params(struct bcm2835_audio_info *sc, uint32_t fmt, uint32_t speed)467{468VC_AUDIO_MSG_T m;469int ret;470471if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {472m.type = VC_AUDIO_MSG_TYPE_CONFIG;473m.u.config.channels = AFMT_CHANNEL(fmt);474m.u.config.samplerate = speed;475m.u.config.bps = AFMT_BIT(fmt);476477ret = vchi_msg_queue(sc->vchi_handle,478&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);479480if (ret != 0)481BCM2835_LOG_ERROR(sc,482"%s: vchi_msg_queue failed (err %d)\n", __func__,483ret);484}485}486487static bool488bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch)489{490491ch->log_vars.slept_for_lack_of_space = 0;492if (ch->playback_state != PLAYBACK_PLAYING)493return (true);494495/* Not enough data */496/* XXXMDC Take unsubmitted stuff into account */497if (sndbuf_getready(ch->buffer)498- MOD_DIFF(499ch->unsubmittedptr,500sndbuf_getreadyptr(ch->buffer),501ch->buffer->bufsize502) < VCHIQ_AUDIO_PACKET_SIZE) {503ch->starved++;504return (true);505}506507/* Not enough free space */508if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) {509ch->log_vars.slept_for_lack_of_space = 1;510return (true);511}512513return (false);514}515516static void517bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count)518{519struct bcm2835_audio_info *sc = ch->parent;520VC_AUDIO_MSG_T m;521int ret;522523if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) {524return;525}526527m.type = VC_AUDIO_MSG_TYPE_WRITE;528m.u.write.count = count;529m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE;530#if defined(__aarch64__)531m.u.write.callback = (uint32_t)(((size_t) ch) >> 32) & 0xffffffff;532m.u.write.cookie = (uint32_t)(((size_t) ch) & 0xffffffff);533#else534m.u.write.callback = (uint32_t) NULL;535m.u.write.cookie = (uint32_t) ch;536#endif537m.u.write.silence = 0;538539ret = vchi_msg_queue(sc->vchi_handle,540&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);541542if (ret != 0)543BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n",544__func__, ret);545546while (count > 0) {547int bytes = MIN((int)m.u.write.max_packet, (int)count);548ret = vchi_msg_queue(sc->vchi_handle,549buf, bytes, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);550if (ret != 0)551BCM2835_LOG_ERROR(sc, "%s: vchi_msg_queue failed: %d\n",552__func__, ret);553buf = (char *)buf + bytes;554count -= bytes;555}556}557558static void559bcm2835_audio_worker(void *data)560{561struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)data;562struct bcm2835_audio_chinfo *ch = &sc->pch;563uint32_t speed, format;564uint32_t volume, dest;565uint32_t flags;566uint32_t count, size, readyptr;567uint8_t *buf;568569ch->playback_state = PLAYBACK_IDLE;570571while (1) {572if (sc->worker_state != WORKER_RUNNING)573break;574575BCM2835_AUDIO_LOCK(sc);576/*577* wait until there are flags set or buffer is ready578* to consume more samples579*/580while ((sc->flags_pending == 0) &&581bcm2835_audio_buffer_should_sleep(ch)) {582cv_wait_sig(&sc->worker_cv, &sc->lock);583if ((sc->flags_pending == 0) &&584(ch->log_vars.slept_for_lack_of_space)) {585BCM2835_LOG_TRACE(sc,586"slept for lack of space\n");587}588}589flags = sc->flags_pending;590/* Clear pending flags */591sc->flags_pending = 0;592BCM2835_AUDIO_UNLOCK(sc);593594/* Requested to change parameters */595if (flags & AUDIO_PARAMS) {596BCM2835_AUDIO_LOCK(sc);597speed = ch->spd;598format = ch->fmt;599volume = sc->volume;600dest = sc->dest;601BCM2835_AUDIO_UNLOCK(sc);602if (ch->playback_state == PLAYBACK_IDLE)603bcm2835_audio_update_params(sc, format, speed);604bcm2835_audio_update_controls(sc, volume, dest);605}606607/* Requested to stop playback */608if ((flags & AUDIO_STOP) &&609(ch->playback_state == PLAYBACK_PLAYING)) {610bcm2835_audio_stop(ch);611BCM2835_AUDIO_LOCK(sc);612bcm2835_audio_reset_channel(&sc->pch);613ch->playback_state = PLAYBACK_IDLE;614long sub_total = ch->submitted_samples;615long retd = ch->retrieved_samples;616BCM2835_AUDIO_UNLOCK(sc);617BCM2835_LOG_INFO(sc,618"stopped audio. submitted a total of %lu "619"having been acked %lu\n", sub_total, retd);620continue;621}622623/* Requested to start playback */624if ((flags & AUDIO_PLAY) &&625(ch->playback_state == PLAYBACK_IDLE)) {626BCM2835_LOG_INFO(sc, "starting audio\n");627unsigned int bsize = ch->buffer->bufsize;628BCM2835_AUDIO_LOCK(sc);629ch->playback_state = PLAYBACK_PLAYING;630ch->log_vars.bsize = bsize;631BCM2835_AUDIO_UNLOCK(sc);632BCM2835_LOG_INFO(sc, "buffer size is %u\n", bsize);633bcm2835_audio_start(ch);634}635636if (ch->playback_state == PLAYBACK_IDLE)637continue;638639if (sndbuf_getready(ch->buffer) == 0)640continue;641642uint32_t i_count;643644/* XXXMDC Take unsubmitted stuff into account */645count = i_count = sndbuf_getready(ch->buffer)646- MOD_DIFF(ch->unsubmittedptr,647sndbuf_getreadyptr(ch->buffer),648ch->buffer->bufsize);649size = ch->buffer->bufsize;650readyptr = ch->unsubmittedptr;651652int size_changed = 0;653unsigned int available;654655BCM2835_AUDIO_LOCK(sc);656if (size != ch->log_vars.bsize) {657ch->log_vars.bsize = size;658size_changed = 1;659}660available = ch->available_space;661/*662* XXXMDC663*664* On arm64, got into situations where665* readyptr was less than a packet away666* from the end of the buffer, which led667* to count being set to 0 and, inexorably, starvation.668* Code below tries to take that into account.669* The problem might have been fixed with some of the670* other changes that were made in the meantime,671* but for now this works fine.672*/673if (readyptr + count > size) {674count = size - readyptr;675}676if(count > ch->available_space){677count = ch->available_space;678count -= (count % VCHIQ_AUDIO_PACKET_SIZE);679}else if (count > VCHIQ_AUDIO_PACKET_SIZE){680count -= (count % VCHIQ_AUDIO_PACKET_SIZE);681}else if (size > count + readyptr) {682count = 0;683}684BCM2835_AUDIO_UNLOCK(sc);685686if (count % VCHIQ_AUDIO_PACKET_SIZE != 0) {687BCM2835_LOG_WARN(sc, "count: %u initial count: %u "688"size: %u readyptr: %u available: %u\n", count,689i_count,size,readyptr,available);690}691if (size_changed)692BCM2835_LOG_INFO(sc, "bsize changed to %u\n", size);693694if (count == 0) {695BCM2835_LOG_WARN(sc,696"not enough room for a packet: count %d,"697" i_count %d, rptr %d, size %d\n",698count, i_count, readyptr, size);699continue;700}701702buf = ch->buffer->buf + readyptr;703704bcm2835_audio_write_samples(ch, buf, count);705BCM2835_AUDIO_LOCK(sc);706ch->unsubmittedptr = (ch->unsubmittedptr + count) %707ch->buffer->bufsize;708ch->available_space -= count;709ch->submitted_samples += count;710long sub = count;711long sub_total = ch->submitted_samples;712long retd = ch->retrieved_samples;713KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space));714BCM2835_AUDIO_UNLOCK(sc);715716BCM2835_LOG_TRACE(sc,717"submitted %lu for a total of %lu having been acked %lu; "718"rptr %d, had %u available\n", sub, sub_total, retd,719readyptr, available);720}721722BCM2835_AUDIO_LOCK(sc);723sc->worker_state = WORKER_STOPPED;724cv_signal(&sc->worker_cv);725BCM2835_AUDIO_UNLOCK(sc);726727kproc_exit(0);728}729730static void731bcm2835_audio_create_worker(struct bcm2835_audio_info *sc)732{733struct proc *newp;734735sc->worker_state = WORKER_RUNNING;736if (kproc_create(bcm2835_audio_worker, (void*)sc, &newp, 0, 0,737"bcm2835_audio_worker") != 0) {738BCM2835_LOG_ERROR(sc,739"failed to create bcm2835_audio_worker\n");740}741}742743/* -------------------------------------------------------------------- */744/* channel interface for VCHI audio */745static void *746bcmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)747{748struct bcm2835_audio_info *sc = devinfo;749struct bcm2835_audio_chinfo *ch = &sc->pch;750void *buffer;751752if (dir == PCMDIR_REC)753return NULL;754755ch->parent = sc;756ch->channel = c;757ch->buffer = b;758759/* default values */760ch->spd = 44100;761ch->fmt = SND_FORMAT(AFMT_S16_LE, 2, 0);762ch->blksz = VCHIQ_AUDIO_PACKET_SIZE;763764buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO);765766if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) {767device_printf(sc->dev, "sndbuf_setup failed\n");768free(buffer, M_DEVBUF);769return NULL;770}771772ch->log_vars = DEFAULT_LOG_VALUES;773774BCM2835_AUDIO_LOCK(sc);775bcm2835_worker_update_params(sc);776BCM2835_AUDIO_UNLOCK(sc);777778return ch;779}780781static int782bcmchan_free(kobj_t obj, void *data)783{784struct bcm2835_audio_chinfo *ch = data;785void *buffer;786787buffer = ch->buffer->buf;788if (buffer)789free(buffer, M_DEVBUF);790791return (0);792}793794static int795bcmchan_setformat(kobj_t obj, void *data, uint32_t format)796{797struct bcm2835_audio_chinfo *ch = data;798struct bcm2835_audio_info *sc = ch->parent;799800BCM2835_AUDIO_LOCK(sc);801ch->fmt = format;802bcm2835_worker_update_params(sc);803BCM2835_AUDIO_UNLOCK(sc);804805return 0;806}807808static uint32_t809bcmchan_setspeed(kobj_t obj, void *data, uint32_t speed)810{811struct bcm2835_audio_chinfo *ch = data;812struct bcm2835_audio_info *sc = ch->parent;813814BCM2835_AUDIO_LOCK(sc);815ch->spd = speed;816bcm2835_worker_update_params(sc);817BCM2835_AUDIO_UNLOCK(sc);818819return ch->spd;820}821822static uint32_t823bcmchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)824{825struct bcm2835_audio_chinfo *ch = data;826827return ch->blksz;828}829830static int831bcmchan_trigger(kobj_t obj, void *data, int go)832{833struct bcm2835_audio_chinfo *ch = data;834struct bcm2835_audio_info *sc = ch->parent;835836if (!PCMTRIG_COMMON(go))837return (0);838839switch (go) {840case PCMTRIG_START:841/* kickstart data flow */842chn_intr(sc->pch.channel);843ch->submitted_samples = 0;844ch->retrieved_samples = 0;845bcm2835_worker_play_start(sc);846break;847848case PCMTRIG_STOP:849case PCMTRIG_ABORT:850bcm2835_worker_play_stop(sc);851break;852853default:854break;855}856return 0;857}858859static uint32_t860bcmchan_getptr(kobj_t obj, void *data)861{862struct bcm2835_audio_chinfo *ch = data;863struct bcm2835_audio_info *sc = ch->parent;864uint32_t ret;865866BCM2835_AUDIO_LOCK(sc);867ret = ch->unsubmittedptr;868BCM2835_AUDIO_UNLOCK(sc);869870return ret;871}872873static struct pcmchan_caps *874bcmchan_getcaps(kobj_t obj, void *data)875{876877return &bcm2835_audio_playcaps;878}879880static kobj_method_t bcmchan_methods[] = {881KOBJMETHOD(channel_init, bcmchan_init),882KOBJMETHOD(channel_free, bcmchan_free),883KOBJMETHOD(channel_setformat, bcmchan_setformat),884KOBJMETHOD(channel_setspeed, bcmchan_setspeed),885KOBJMETHOD(channel_setblocksize, bcmchan_setblocksize),886KOBJMETHOD(channel_trigger, bcmchan_trigger),887KOBJMETHOD(channel_getptr, bcmchan_getptr),888KOBJMETHOD(channel_getcaps, bcmchan_getcaps),889KOBJMETHOD_END890};891CHANNEL_DECLARE(bcmchan);892893/************************************************************/894895static int896bcmmix_init(struct snd_mixer *m)897{898899mix_setdevs(m, SOUND_MASK_VOLUME);900901return (0);902}903904static int905bcmmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)906{907struct bcm2835_audio_info *sc = mix_getdevinfo(m);908909switch (dev) {910case SOUND_MIXER_VOLUME:911BCM2835_AUDIO_LOCK(sc);912sc->volume = left;913bcm2835_worker_update_params(sc);914BCM2835_AUDIO_UNLOCK(sc);915916break;917918default:919break;920}921922return left | (left << 8);923}924925static kobj_method_t bcmmixer_methods[] = {926KOBJMETHOD(mixer_init, bcmmix_init),927KOBJMETHOD(mixer_set, bcmmix_set),928KOBJMETHOD_END929};930931MIXER_DECLARE(bcmmixer);932933static int934sysctl_bcm2835_audio_dest(SYSCTL_HANDLER_ARGS)935{936struct bcm2835_audio_info *sc = arg1;937int val;938int err;939940val = sc->dest;941err = sysctl_handle_int(oidp, &val, 0, req);942if (err || !req->newptr) /* error || read request */943return (err);944945if ((val < 0) || (val > 2))946return (EINVAL);947948BCM2835_AUDIO_LOCK(sc);949sc->dest = val;950bcm2835_worker_update_params(sc);951BCM2835_AUDIO_UNLOCK(sc);952953if (bootverbose)954device_printf(sc->dev, "destination set to %s\n", dest_description(val));955956return (0);957}958959static void960vchi_audio_sysctl_init(struct bcm2835_audio_info *sc)961{962struct sysctl_ctx_list *ctx;963struct sysctl_oid *tree_node;964struct sysctl_oid_list *tree;965966/*967* Add system sysctl tree/handlers.968*/969ctx = device_get_sysctl_ctx(sc->dev);970tree_node = device_get_sysctl_tree(sc->dev);971tree = SYSCTL_CHILDREN(tree_node);972SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "dest",973CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_NEEDGIANT, sc, sizeof(*sc),974sysctl_bcm2835_audio_dest, "IU", "audio destination, "975"0 - auto, 1 - headphones, 2 - HDMI");976SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "callbacks",977CTLFLAG_RD, &sc->pch.callbacks,978"callbacks total");979SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "submitted",980CTLFLAG_RD, &sc->pch.submitted_samples,981"last play submitted samples");982SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "retrieved",983CTLFLAG_RD, &sc->pch.retrieved_samples,984"last play retrieved samples");985SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "underruns",986CTLFLAG_RD, &sc->pch.underruns,987"callback underruns");988SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "freebuffer",989CTLFLAG_RD, &sc->pch.available_space,990sc->pch.available_space, "callbacks total");991SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "starved",992CTLFLAG_RD, &sc->pch.starved,993sc->pch.starved, "number of starved conditions");994SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "trace",995CTLFLAG_RW, &sc->verbose_trace,996sc->verbose_trace, "enable tracing of transfers");997}998999static void1000bcm2835_audio_identify(driver_t *driver, device_t parent)1001{10021003BUS_ADD_CHILD(parent, 0, "pcm", 0);1004}10051006static int1007bcm2835_audio_probe(device_t dev)1008{10091010device_set_desc(dev, "VCHIQ audio");1011return (BUS_PROBE_DEFAULT);1012}10131014static void1015bcm2835_audio_delayed_init(void *xsc)1016{1017struct bcm2835_audio_info *sc;1018char status[SND_STATUSLEN];10191020sc = xsc;10211022config_intrhook_disestablish(&sc->intr_hook);10231024bcm2835_audio_init(sc);1025bcm2835_audio_open(sc);1026sc->volume = 75;1027sc->dest = DEST_AUTO;1028sc->verbose_trace = 0;10291030if (mixer_init(sc->dev, &bcmmixer_class, sc)) {1031device_printf(sc->dev, "mixer_init failed\n");1032goto no;1033}10341035pcm_init(sc->dev, sc);10361037pcm_addchan(sc->dev, PCMDIR_PLAY, &bcmchan_class, sc);1038snprintf(status, SND_STATUSLEN, "at VCHIQ");1039if (pcm_register(sc->dev, status)) {1040device_printf(sc->dev, "pcm_register failed\n");1041goto no;1042}10431044bcm2835_audio_reset_channel(&sc->pch);1045bcm2835_audio_create_worker(sc);10461047vchi_audio_sysctl_init(sc);10481049no:1050;1051}10521053static int1054bcm2835_audio_attach(device_t dev)1055{1056struct bcm2835_audio_info *sc;10571058sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);10591060sc->dev = dev;1061sc->bufsz = VCHIQ_AUDIO_BUFFER_SIZE;10621063mtx_init(&sc->lock, device_get_nameunit(dev),1064"bcm_audio_lock", MTX_DEF);1065cv_init(&sc->worker_cv, "worker_cv");1066sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;10671068/*1069* We need interrupts enabled for VCHI to work properly,1070* so delay initialization until it happens.1071*/1072sc->intr_hook.ich_func = bcm2835_audio_delayed_init;1073sc->intr_hook.ich_arg = sc;10741075if (config_intrhook_establish(&sc->intr_hook) != 0)1076goto no;10771078return 0;10791080no:1081return ENXIO;1082}10831084static int1085bcm2835_audio_detach(device_t dev)1086{1087int r;1088struct bcm2835_audio_info *sc;1089sc = pcm_getdevinfo(dev);10901091/* Stop worker thread */1092BCM2835_AUDIO_LOCK(sc);1093sc->worker_state = WORKER_STOPPING;1094cv_signal(&sc->worker_cv);1095/* Wait for thread to exit */1096while (sc->worker_state != WORKER_STOPPED)1097cv_wait_sig(&sc->worker_cv, &sc->lock);1098BCM2835_AUDIO_UNLOCK(sc);10991100r = pcm_unregister(dev);1101if (r)1102return r;11031104mtx_destroy(&sc->lock);1105cv_destroy(&sc->worker_cv);11061107bcm2835_audio_release(sc);11081109free(sc, M_DEVBUF);11101111return 0;1112}11131114static device_method_t bcm2835_audio_methods[] = {1115/* Device interface */1116DEVMETHOD(device_identify, bcm2835_audio_identify),1117DEVMETHOD(device_probe, bcm2835_audio_probe),1118DEVMETHOD(device_attach, bcm2835_audio_attach),1119DEVMETHOD(device_detach, bcm2835_audio_detach),1120{ 0, 0 }1121};11221123static driver_t bcm2835_audio_driver = {1124"pcm",1125bcm2835_audio_methods,1126PCM_SOFTC_SIZE,1127};11281129DRIVER_MODULE(bcm2835_audio, vchiq, bcm2835_audio_driver, 0, 0);1130MODULE_DEPEND(bcm2835_audio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);1131MODULE_DEPEND(bcm2835_audio, vchiq, 1, 1, 1);1132MODULE_VERSION(bcm2835_audio, 1);113311341135