Path: blob/main/sys/arm/broadcom/bcm2835/bcm2835_audio.c
39566 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;115};116117struct bcm2835_audio_info {118device_t dev;119unsigned int bufsz;120struct bcm2835_audio_chinfo pch;121uint32_t dest, volume;122struct intr_config_hook intr_hook;123124/* VCHI data */125VCHI_INSTANCE_T vchi_instance;126VCHI_CONNECTION_T *vchi_connection;127VCHI_SERVICE_HANDLE_T vchi_handle;128129struct mtx lock;130struct cv worker_cv;131132uint32_t flags_pending;133134/* Worker thread state */135int worker_state;136};137138#define BCM2835_AUDIO_LOCK(sc) mtx_lock(&(sc)->lock)139#define BCM2835_AUDIO_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED)140#define BCM2835_AUDIO_UNLOCK(sc) mtx_unlock(&(sc)->lock)141142static const char *143dest_description(uint32_t dest)144{145switch (dest) {146case DEST_AUTO:147return "AUTO";148break;149150case DEST_HEADPHONES:151return "HEADPHONES";152break;153154case DEST_HDMI:155return "HDMI";156break;157default:158return "UNKNOWN";159break;160}161}162163static void164bcm2835_worker_update_params(struct bcm2835_audio_info *sc)165{166167BCM2835_AUDIO_LOCKED(sc);168169sc->flags_pending |= AUDIO_PARAMS;170cv_signal(&sc->worker_cv);171}172173static void174bcm2835_worker_play_start(struct bcm2835_audio_info *sc)175{176BCM2835_AUDIO_LOCK(sc);177sc->flags_pending &= ~(AUDIO_STOP);178sc->flags_pending |= AUDIO_PLAY;179cv_signal(&sc->worker_cv);180BCM2835_AUDIO_UNLOCK(sc);181}182183static void184bcm2835_worker_play_stop(struct bcm2835_audio_info *sc)185{186BCM2835_AUDIO_LOCK(sc);187sc->flags_pending &= ~(AUDIO_PLAY);188sc->flags_pending |= AUDIO_STOP;189cv_signal(&sc->worker_cv);190BCM2835_AUDIO_UNLOCK(sc);191}192193static void194bcm2835_audio_callback(void *param, const VCHI_CALLBACK_REASON_T reason, void *msg_handle)195{196struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)param;197int32_t status;198uint32_t msg_len;199VC_AUDIO_MSG_T m;200201if (reason != VCHI_CALLBACK_MSG_AVAILABLE)202return;203204status = vchi_msg_dequeue(sc->vchi_handle,205&m, sizeof m, &msg_len, VCHI_FLAGS_NONE);206if (status != 0)207return;208if (m.type == VC_AUDIO_MSG_TYPE_RESULT) {209if (m.u.result.success) {210device_printf(sc->dev,211"msg type %08x failed\n",212m.type);213}214} else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) {215struct bcm2835_audio_chinfo *ch = m.u.complete.cookie;216217int count = m.u.complete.count & 0xffff;218int perr = (m.u.complete.count & (1U << 30)) != 0;219ch->callbacks++;220if (perr)221ch->underruns++;222223BCM2835_AUDIO_LOCK(sc);224if (ch->playback_state != PLAYBACK_IDLE) {225/* Prevent LOR */226BCM2835_AUDIO_UNLOCK(sc);227chn_intr(sc->pch.channel);228BCM2835_AUDIO_LOCK(sc);229}230/* We should check again, state might have changed */231if (ch->playback_state != PLAYBACK_IDLE) {232if (!perr) {233if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) {234device_printf(sc->dev, "inconsistent data in callback:\n");235device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n",236ch->available_space, count, perr);237device_printf(sc->dev,238"retrieved_samples = %lld, submitted_samples = %lld\n",239ch->retrieved_samples, ch->submitted_samples);240}241ch->available_space += count;242ch->retrieved_samples += count;243}244if (perr || (ch->available_space >= VCHIQ_AUDIO_PACKET_SIZE))245cv_signal(&sc->worker_cv);246}247BCM2835_AUDIO_UNLOCK(sc);248} else249printf("%s: unknown m.type: %d\n", __func__, m.type);250}251252/* VCHIQ stuff */253static void254bcm2835_audio_init(struct bcm2835_audio_info *sc)255{256int status;257258/* Initialize and create a VCHI connection */259status = vchi_initialise(&sc->vchi_instance);260if (status != 0) {261printf("vchi_initialise failed: %d\n", status);262return;263}264265status = vchi_connect(NULL, 0, sc->vchi_instance);266if (status != 0) {267printf("vchi_connect failed: %d\n", status);268return;269}270271SERVICE_CREATION_T params = {272VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER),273VC_AUDIO_SERVER_NAME, /* 4cc service code */274sc->vchi_connection, /* passed in fn pointers */2750, /* rx fifo size */2760, /* tx fifo size */277bcm2835_audio_callback, /* service callback */278sc, /* service callback parameter */2791,2801,2810 /* want crc check on bulk transfers */282};283284status = vchi_service_open(sc->vchi_instance, ¶ms,285&sc->vchi_handle);286287if (status != 0)288sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;289}290291static void292bcm2835_audio_release(struct bcm2835_audio_info *sc)293{294int success;295296if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {297success = vchi_service_close(sc->vchi_handle);298if (success != 0)299printf("vchi_service_close failed: %d\n", success);300vchi_service_release(sc->vchi_handle);301sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;302}303304vchi_disconnect(sc->vchi_instance);305}306307static void308bcm2835_audio_reset_channel(struct bcm2835_audio_chinfo *ch)309{310311ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE;312ch->unsubmittedptr = 0;313sndbuf_reset(ch->buffer);314}315316static void317bcm2835_audio_start(struct bcm2835_audio_chinfo *ch)318{319VC_AUDIO_MSG_T m;320int ret;321struct bcm2835_audio_info *sc = ch->parent;322323if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {324m.type = VC_AUDIO_MSG_TYPE_START;325ret = vchi_msg_queue(sc->vchi_handle,326&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);327328if (ret != 0)329printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);330}331}332333static void334bcm2835_audio_stop(struct bcm2835_audio_chinfo *ch)335{336VC_AUDIO_MSG_T m;337int ret;338struct bcm2835_audio_info *sc = ch->parent;339340if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {341m.type = VC_AUDIO_MSG_TYPE_STOP;342m.u.stop.draining = 0;343344ret = vchi_msg_queue(sc->vchi_handle,345&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);346347if (ret != 0)348printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);349}350}351352static void353bcm2835_audio_open(struct bcm2835_audio_info *sc)354{355VC_AUDIO_MSG_T m;356int ret;357358if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {359m.type = VC_AUDIO_MSG_TYPE_OPEN;360ret = vchi_msg_queue(sc->vchi_handle,361&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);362363if (ret != 0)364printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);365}366}367368static void369bcm2835_audio_update_controls(struct bcm2835_audio_info *sc, uint32_t volume, uint32_t dest)370{371VC_AUDIO_MSG_T m;372int ret, db;373374if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {375m.type = VC_AUDIO_MSG_TYPE_CONTROL;376m.u.control.dest = dest;377if (volume > 99)378volume = 99;379db = db_levels[volume/5];380m.u.control.volume = VCHIQ_AUDIO_VOLUME(db);381382ret = vchi_msg_queue(sc->vchi_handle,383&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);384385if (ret != 0)386printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);387}388}389390static void391bcm2835_audio_update_params(struct bcm2835_audio_info *sc, uint32_t fmt, uint32_t speed)392{393VC_AUDIO_MSG_T m;394int ret;395396if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) {397m.type = VC_AUDIO_MSG_TYPE_CONFIG;398m.u.config.channels = AFMT_CHANNEL(fmt);399m.u.config.samplerate = speed;400m.u.config.bps = AFMT_BIT(fmt);401402ret = vchi_msg_queue(sc->vchi_handle,403&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);404405if (ret != 0)406printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);407}408}409410static bool411bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch)412{413414if (ch->playback_state != PLAYBACK_PLAYING)415return (true);416417/* Not enough data */418if (sndbuf_getready(ch->buffer) < VCHIQ_AUDIO_PACKET_SIZE) {419printf("starve\n");420ch->starved++;421return (true);422}423424/* Not enough free space */425if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) {426return (true);427}428429return (false);430}431432static void433bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count)434{435struct bcm2835_audio_info *sc = ch->parent;436VC_AUDIO_MSG_T m;437int ret;438439if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) {440return;441}442443m.type = VC_AUDIO_MSG_TYPE_WRITE;444m.u.write.count = count;445m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE;446m.u.write.callback = NULL;447m.u.write.cookie = ch;448m.u.write.silence = 0;449450ret = vchi_msg_queue(sc->vchi_handle,451&m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);452453if (ret != 0)454printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret);455456while (count > 0) {457int bytes = MIN((int)m.u.write.max_packet, (int)count);458ret = vchi_msg_queue(sc->vchi_handle,459buf, bytes, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL);460if (ret != 0)461printf("%s: vchi_msg_queue failed: %d\n",462__func__, ret);463buf = (char *)buf + bytes;464count -= bytes;465}466}467468static void469bcm2835_audio_worker(void *data)470{471struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)data;472struct bcm2835_audio_chinfo *ch = &sc->pch;473uint32_t speed, format;474uint32_t volume, dest;475uint32_t flags;476uint32_t count, size, readyptr;477uint8_t *buf;478479ch->playback_state = PLAYBACK_IDLE;480481while (1) {482if (sc->worker_state != WORKER_RUNNING)483break;484485BCM2835_AUDIO_LOCK(sc);486/*487* wait until there are flags set or buffer is ready488* to consume more samples489*/490while ((sc->flags_pending == 0) &&491bcm2835_audio_buffer_should_sleep(ch)) {492cv_wait_sig(&sc->worker_cv, &sc->lock);493}494flags = sc->flags_pending;495/* Clear pending flags */496sc->flags_pending = 0;497BCM2835_AUDIO_UNLOCK(sc);498499/* Requested to change parameters */500if (flags & AUDIO_PARAMS) {501BCM2835_AUDIO_LOCK(sc);502speed = ch->spd;503format = ch->fmt;504volume = sc->volume;505dest = sc->dest;506BCM2835_AUDIO_UNLOCK(sc);507if (ch->playback_state == PLAYBACK_IDLE)508bcm2835_audio_update_params(sc, format, speed);509bcm2835_audio_update_controls(sc, volume, dest);510}511512/* Requested to stop playback */513if ((flags & AUDIO_STOP) &&514(ch->playback_state == PLAYBACK_PLAYING)) {515bcm2835_audio_stop(ch);516BCM2835_AUDIO_LOCK(sc);517bcm2835_audio_reset_channel(&sc->pch);518ch->playback_state = PLAYBACK_IDLE;519BCM2835_AUDIO_UNLOCK(sc);520continue;521}522523/* Requested to start playback */524if ((flags & AUDIO_PLAY) &&525(ch->playback_state == PLAYBACK_IDLE)) {526BCM2835_AUDIO_LOCK(sc);527ch->playback_state = PLAYBACK_PLAYING;528BCM2835_AUDIO_UNLOCK(sc);529bcm2835_audio_start(ch);530}531532if (ch->playback_state == PLAYBACK_IDLE)533continue;534535if (sndbuf_getready(ch->buffer) == 0)536continue;537538count = sndbuf_getready(ch->buffer);539size = sndbuf_getsize(ch->buffer);540readyptr = sndbuf_getreadyptr(ch->buffer);541542BCM2835_AUDIO_LOCK(sc);543if (readyptr + count > size)544count = size - readyptr;545count = min(count, ch->available_space);546count -= (count % VCHIQ_AUDIO_PACKET_SIZE);547BCM2835_AUDIO_UNLOCK(sc);548549if (count < VCHIQ_AUDIO_PACKET_SIZE)550continue;551552buf = (uint8_t*)sndbuf_getbuf(ch->buffer) + readyptr;553554bcm2835_audio_write_samples(ch, buf, count);555BCM2835_AUDIO_LOCK(sc);556ch->unsubmittedptr = (ch->unsubmittedptr + count) % sndbuf_getsize(ch->buffer);557ch->available_space -= count;558ch->submitted_samples += count;559KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space));560BCM2835_AUDIO_UNLOCK(sc);561}562563BCM2835_AUDIO_LOCK(sc);564sc->worker_state = WORKER_STOPPED;565cv_signal(&sc->worker_cv);566BCM2835_AUDIO_UNLOCK(sc);567568kproc_exit(0);569}570571static void572bcm2835_audio_create_worker(struct bcm2835_audio_info *sc)573{574struct proc *newp;575576sc->worker_state = WORKER_RUNNING;577if (kproc_create(bcm2835_audio_worker, (void*)sc, &newp, 0, 0,578"bcm2835_audio_worker") != 0) {579printf("failed to create bcm2835_audio_worker\n");580}581}582583/* -------------------------------------------------------------------- */584/* channel interface for VCHI audio */585static void *586bcmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)587{588struct bcm2835_audio_info *sc = devinfo;589struct bcm2835_audio_chinfo *ch = &sc->pch;590void *buffer;591592if (dir == PCMDIR_REC)593return NULL;594595ch->parent = sc;596ch->channel = c;597ch->buffer = b;598599/* default values */600ch->spd = 44100;601ch->fmt = SND_FORMAT(AFMT_S16_LE, 2, 0);602ch->blksz = VCHIQ_AUDIO_PACKET_SIZE;603604buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO);605606if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) {607device_printf(sc->dev, "sndbuf_setup failed\n");608free(buffer, M_DEVBUF);609return NULL;610}611612BCM2835_AUDIO_LOCK(sc);613bcm2835_worker_update_params(sc);614BCM2835_AUDIO_UNLOCK(sc);615616return ch;617}618619static int620bcmchan_free(kobj_t obj, void *data)621{622struct bcm2835_audio_chinfo *ch = data;623void *buffer;624625buffer = sndbuf_getbuf(ch->buffer);626if (buffer)627free(buffer, M_DEVBUF);628629return (0);630}631632static int633bcmchan_setformat(kobj_t obj, void *data, uint32_t format)634{635struct bcm2835_audio_chinfo *ch = data;636struct bcm2835_audio_info *sc = ch->parent;637638BCM2835_AUDIO_LOCK(sc);639ch->fmt = format;640bcm2835_worker_update_params(sc);641BCM2835_AUDIO_UNLOCK(sc);642643return 0;644}645646static uint32_t647bcmchan_setspeed(kobj_t obj, void *data, uint32_t speed)648{649struct bcm2835_audio_chinfo *ch = data;650struct bcm2835_audio_info *sc = ch->parent;651652BCM2835_AUDIO_LOCK(sc);653ch->spd = speed;654bcm2835_worker_update_params(sc);655BCM2835_AUDIO_UNLOCK(sc);656657return ch->spd;658}659660static uint32_t661bcmchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)662{663struct bcm2835_audio_chinfo *ch = data;664665return ch->blksz;666}667668static int669bcmchan_trigger(kobj_t obj, void *data, int go)670{671struct bcm2835_audio_chinfo *ch = data;672struct bcm2835_audio_info *sc = ch->parent;673674if (!PCMTRIG_COMMON(go))675return (0);676677switch (go) {678case PCMTRIG_START:679/* kickstart data flow */680chn_intr(sc->pch.channel);681ch->submitted_samples = 0;682ch->retrieved_samples = 0;683bcm2835_worker_play_start(sc);684break;685686case PCMTRIG_STOP:687case PCMTRIG_ABORT:688bcm2835_worker_play_stop(sc);689break;690691default:692break;693}694return 0;695}696697static uint32_t698bcmchan_getptr(kobj_t obj, void *data)699{700struct bcm2835_audio_chinfo *ch = data;701struct bcm2835_audio_info *sc = ch->parent;702uint32_t ret;703704BCM2835_AUDIO_LOCK(sc);705ret = ch->unsubmittedptr;706BCM2835_AUDIO_UNLOCK(sc);707708return ret;709}710711static struct pcmchan_caps *712bcmchan_getcaps(kobj_t obj, void *data)713{714715return &bcm2835_audio_playcaps;716}717718static kobj_method_t bcmchan_methods[] = {719KOBJMETHOD(channel_init, bcmchan_init),720KOBJMETHOD(channel_free, bcmchan_free),721KOBJMETHOD(channel_setformat, bcmchan_setformat),722KOBJMETHOD(channel_setspeed, bcmchan_setspeed),723KOBJMETHOD(channel_setblocksize, bcmchan_setblocksize),724KOBJMETHOD(channel_trigger, bcmchan_trigger),725KOBJMETHOD(channel_getptr, bcmchan_getptr),726KOBJMETHOD(channel_getcaps, bcmchan_getcaps),727KOBJMETHOD_END728};729CHANNEL_DECLARE(bcmchan);730731/************************************************************/732733static int734bcmmix_init(struct snd_mixer *m)735{736737mix_setdevs(m, SOUND_MASK_VOLUME);738739return (0);740}741742static int743bcmmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)744{745struct bcm2835_audio_info *sc = mix_getdevinfo(m);746747switch (dev) {748case SOUND_MIXER_VOLUME:749BCM2835_AUDIO_LOCK(sc);750sc->volume = left;751bcm2835_worker_update_params(sc);752BCM2835_AUDIO_UNLOCK(sc);753754break;755756default:757break;758}759760return left | (left << 8);761}762763static kobj_method_t bcmmixer_methods[] = {764KOBJMETHOD(mixer_init, bcmmix_init),765KOBJMETHOD(mixer_set, bcmmix_set),766KOBJMETHOD_END767};768769MIXER_DECLARE(bcmmixer);770771static int772sysctl_bcm2835_audio_dest(SYSCTL_HANDLER_ARGS)773{774struct bcm2835_audio_info *sc = arg1;775int val;776int err;777778val = sc->dest;779err = sysctl_handle_int(oidp, &val, 0, req);780if (err || !req->newptr) /* error || read request */781return (err);782783if ((val < 0) || (val > 2))784return (EINVAL);785786BCM2835_AUDIO_LOCK(sc);787sc->dest = val;788bcm2835_worker_update_params(sc);789BCM2835_AUDIO_UNLOCK(sc);790791if (bootverbose)792device_printf(sc->dev, "destination set to %s\n", dest_description(val));793794return (0);795}796797static void798vchi_audio_sysctl_init(struct bcm2835_audio_info *sc)799{800struct sysctl_ctx_list *ctx;801struct sysctl_oid *tree_node;802struct sysctl_oid_list *tree;803804/*805* Add system sysctl tree/handlers.806*/807ctx = device_get_sysctl_ctx(sc->dev);808tree_node = device_get_sysctl_tree(sc->dev);809tree = SYSCTL_CHILDREN(tree_node);810SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "dest",811CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_NEEDGIANT, sc, sizeof(*sc),812sysctl_bcm2835_audio_dest, "IU", "audio destination, "813"0 - auto, 1 - headphones, 2 - HDMI");814SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "callbacks",815CTLFLAG_RD, &sc->pch.callbacks,816"callbacks total");817SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "submitted",818CTLFLAG_RD, &sc->pch.submitted_samples,819"last play submitted samples");820SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "retrieved",821CTLFLAG_RD, &sc->pch.retrieved_samples,822"last play retrieved samples");823SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "underruns",824CTLFLAG_RD, &sc->pch.underruns,825"callback underruns");826SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "freebuffer",827CTLFLAG_RD, &sc->pch.available_space,828sc->pch.available_space, "callbacks total");829SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "starved",830CTLFLAG_RD, &sc->pch.starved,831sc->pch.starved, "number of starved conditions");832}833834static void835bcm2835_audio_identify(driver_t *driver, device_t parent)836{837838BUS_ADD_CHILD(parent, 0, "pcm", 0);839}840841static int842bcm2835_audio_probe(device_t dev)843{844845device_set_desc(dev, "VCHIQ audio");846return (BUS_PROBE_DEFAULT);847}848849static void850bcm2835_audio_delayed_init(void *xsc)851{852struct bcm2835_audio_info *sc;853char status[SND_STATUSLEN];854855sc = xsc;856857config_intrhook_disestablish(&sc->intr_hook);858859bcm2835_audio_init(sc);860bcm2835_audio_open(sc);861sc->volume = 75;862sc->dest = DEST_AUTO;863864if (mixer_init(sc->dev, &bcmmixer_class, sc)) {865device_printf(sc->dev, "mixer_init failed\n");866goto no;867}868869pcm_init(sc->dev, sc);870871pcm_addchan(sc->dev, PCMDIR_PLAY, &bcmchan_class, sc);872snprintf(status, SND_STATUSLEN, "at VCHIQ");873if (pcm_register(sc->dev, status)) {874device_printf(sc->dev, "pcm_register failed\n");875goto no;876}877878bcm2835_audio_reset_channel(&sc->pch);879bcm2835_audio_create_worker(sc);880881vchi_audio_sysctl_init(sc);882883no:884;885}886887static int888bcm2835_audio_attach(device_t dev)889{890struct bcm2835_audio_info *sc;891892sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);893894sc->dev = dev;895sc->bufsz = VCHIQ_AUDIO_BUFFER_SIZE;896897mtx_init(&sc->lock, device_get_nameunit(dev),898"bcm_audio_lock", MTX_DEF);899cv_init(&sc->worker_cv, "worker_cv");900sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID;901902/*903* We need interrupts enabled for VCHI to work properly,904* so delay initialization until it happens.905*/906sc->intr_hook.ich_func = bcm2835_audio_delayed_init;907sc->intr_hook.ich_arg = sc;908909if (config_intrhook_establish(&sc->intr_hook) != 0)910goto no;911912return 0;913914no:915return ENXIO;916}917918static int919bcm2835_audio_detach(device_t dev)920{921int r;922struct bcm2835_audio_info *sc;923sc = pcm_getdevinfo(dev);924925/* Stop worker thread */926BCM2835_AUDIO_LOCK(sc);927sc->worker_state = WORKER_STOPPING;928cv_signal(&sc->worker_cv);929/* Wait for thread to exit */930while (sc->worker_state != WORKER_STOPPED)931cv_wait_sig(&sc->worker_cv, &sc->lock);932BCM2835_AUDIO_UNLOCK(sc);933934r = pcm_unregister(dev);935if (r)936return r;937938mtx_destroy(&sc->lock);939cv_destroy(&sc->worker_cv);940941bcm2835_audio_release(sc);942943free(sc, M_DEVBUF);944945return 0;946}947948static device_method_t bcm2835_audio_methods[] = {949/* Device interface */950DEVMETHOD(device_identify, bcm2835_audio_identify),951DEVMETHOD(device_probe, bcm2835_audio_probe),952DEVMETHOD(device_attach, bcm2835_audio_attach),953DEVMETHOD(device_detach, bcm2835_audio_detach),954{ 0, 0 }955};956957static driver_t bcm2835_audio_driver = {958"pcm",959bcm2835_audio_methods,960PCM_SOFTC_SIZE,961};962963DRIVER_MODULE(bcm2835_audio, vchiq, bcm2835_audio_driver, 0, 0);964MODULE_DEPEND(bcm2835_audio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);965MODULE_DEPEND(bcm2835_audio, vchiq, 1, 1, 1);966MODULE_VERSION(bcm2835_audio, 1);967968969