diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 9945cb804d10..5158d87298d2 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -10,7 +10,7 @@ greybus-y := core.o \ protocol.o \ operation.o -gb-phy-y := gpb.o \ +gb-phy-y := gpb.o \ sdio.o \ uart.o \ pwm.o \ @@ -18,7 +18,11 @@ gb-phy-y := gpb.o \ hid.o \ i2c.o \ spi.o \ - usb.o + usb.o \ + audio.o \ + audio-pcm.o \ + audio-dai.o \ + audio-gb-cmds.o # Prefix all modules with gb- gb-vibrator-y := vibrator.o diff --git a/drivers/staging/greybus/audio-dai.c b/drivers/staging/greybus/audio-dai.c new file mode 100644 index 000000000000..f2c8ca0b4df6 --- /dev/null +++ b/drivers/staging/greybus/audio-dai.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "greybus.h" +#include "gpbridge.h" +#include "audio.h" + +/* + * This is the greybus cpu dai logic. It really doesn't do much + * other then provide the TRIGGER_START/STOP hooks that start + * and stop the timer sending audio data in the pcm logic. + */ + + +static int gb_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct gb_snd *snd_dev; + + + snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + gb_pcm_hrtimer_start(snd_dev); + break; + case SNDRV_PCM_TRIGGER_STOP: + gb_pcm_hrtimer_stop(snd_dev); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * XXX This is annoying, if we don't have a set_fmt function + * the subsystem returns -ENOTSUPP, which causes applications + * to fail, so add a dummy function here. + */ +static int gb_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return 0; +} + +static const struct snd_soc_dai_ops gb_dai_ops = { + .trigger = gb_dai_trigger, + .set_fmt = gb_dai_set_fmt, +}; + +struct snd_soc_dai_driver gb_cpu_dai = { + .name = "gb-cpu-dai", + .playback = { + .rates = GB_RATES, + .formats = GB_FMTS, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &gb_dai_ops, +}; + +static const struct snd_soc_component_driver gb_soc_component = { + .name = "gb-component", +}; + +static int gb_plat_probe(struct platform_device *pdev) +{ + struct gb_snd *snd_dev; + int ret; + + snd_dev = (struct gb_snd *)pdev->dev.platform_data; + dev_set_drvdata(&pdev->dev, snd_dev); + + ret = snd_soc_register_component(&pdev->dev, &gb_soc_component, + &gb_cpu_dai, 1); + return ret; +} + +static int gb_plat_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +struct platform_driver gb_audio_plat_driver = { + .driver = { + .name = "gb-dai-audio", + }, + .probe = gb_plat_probe, + .remove = gb_plat_remove, +}; diff --git a/drivers/staging/greybus/audio-gb-cmds.c b/drivers/staging/greybus/audio-gb-cmds.c new file mode 100644 index 000000000000..ef3191670485 --- /dev/null +++ b/drivers/staging/greybus/audio-gb-cmds.c @@ -0,0 +1,207 @@ +#include +#include "greybus.h" +#include "gpbridge.h" +#include "audio.h" + +#define GB_I2S_MGMT_VERSION_MAJOR 0x00 +#define GB_I2S_MGMT_VERSION_MINOR 0x01 + +#define GB_I2S_DATA_VERSION_MAJOR 0x00 +#define GB_I2S_MGMT_VERSION_MINOR 0x01 + +/*********************************** + * GB I2S helper functions + ***********************************/ +int gb_i2s_mgmt_get_version(struct gb_connection *connection) +{ + struct gb_protocol_version_response response; + + memset(&response, 0, sizeof(response)); + return gb_protocol_get_version(connection, + GB_I2S_MGMT_TYPE_PROTOCOL_VERSION, + NULL, 0, &response, + GB_I2S_MGMT_VERSION_MAJOR); +} + +int gb_i2s_data_get_version(struct gb_connection *connection) +{ + struct gb_protocol_version_response response; + + memset(&response, 0, sizeof(response)); + return gb_protocol_get_version(connection, + GB_I2S_DATA_TYPE_PROTOCOL_VERSION, + NULL, 0, &response, + GB_I2S_DATA_VERSION_MAJOR); +} + +int gb_i2s_mgmt_activate_cport(struct gb_connection *connection, + uint16_t cport) +{ + struct gb_i2s_mgmt_activate_cport_request request; + + memset(&request, 0, sizeof(request)); + request.cport = cport; + + return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_ACTIVATE_CPORT, + &request, sizeof(request), NULL, 0); +} + +int gb_i2s_mgmt_deactivate_cport(struct gb_connection *connection, + uint16_t cport) +{ + struct gb_i2s_mgmt_deactivate_cport_request request; + + memset(&request, 0, sizeof(request)); + request.cport = cport; + + return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_DEACTIVATE_CPORT, + &request, sizeof(request), NULL, 0); +} + +int gb_i2s_mgmt_get_supported_configurations( + struct gb_connection *connection, + struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg, + size_t size) +{ + return gb_operation_sync(connection, + GB_I2S_MGMT_TYPE_GET_SUPPORTED_CONFIGURATIONS, + NULL, 0, get_cfg, size); +} + +int gb_i2s_mgmt_set_configuration(struct gb_connection *connection, + struct gb_i2s_mgmt_set_configuration_request *set_cfg) +{ + return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_SET_CONFIGURATION, + set_cfg, sizeof(*set_cfg), NULL, 0); +} + +int gb_i2s_mgmt_set_samples_per_message( + struct gb_connection *connection, + uint16_t samples_per_message) +{ + struct gb_i2s_mgmt_set_samples_per_message_request request; + + memset(&request, 0, sizeof(request)); + request.samples_per_message = samples_per_message; + + return gb_operation_sync(connection, + GB_I2S_MGMT_TYPE_SET_SAMPLES_PER_MESSAGE, + &request, sizeof(request), NULL, 0); +} + +/* + * XXX This is sort of a generic "setup" function which probably needs + * to be broken up, and tied into the constraints. + * + * I'm on the fence if we should just dictate that we only support + * 48k, 16bit, 2 channel, and avoid doign the whole probe for configurations + * and then picking one. + */ +int gb_i2s_mgmt_setup(struct gb_connection *connection) +{ + struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg; + struct gb_i2s_mgmt_set_configuration_request set_cfg; + struct gb_i2s_mgmt_configuration *cfg; + size_t size; + int i, ret; + + size = sizeof(*get_cfg) + + (CONFIG_COUNT_MAX * sizeof(get_cfg->config[0])); + + get_cfg = kzalloc(size, GFP_KERNEL); + if (!get_cfg) + return -ENOMEM; + + ret = gb_i2s_mgmt_get_supported_configurations(connection, get_cfg, + size); + if (ret) { + pr_err("get_supported_config failed: %d\n", ret); + goto free_get_cfg; + } + + /* Pick 48KHz 16-bits/channel */ + for (i = 0, cfg = get_cfg->config; i < CONFIG_COUNT_MAX; i++, cfg++) { + if ((cfg->sample_frequency == GB_SAMPLE_RATE) && + (cfg->num_channels == 2) && + (cfg->bytes_per_channel == 2) && + (cfg->byte_order & GB_I2S_MGMT_BYTE_ORDER_LE) && + (cfg->spatial_locations == + (GB_I2S_MGMT_SPATIAL_LOCATION_FL | + GB_I2S_MGMT_SPATIAL_LOCATION_FR)) && + (cfg->ll_protocol & GB_I2S_MGMT_PROTOCOL_I2S) && + (cfg->ll_mclk_role & GB_I2S_MGMT_ROLE_MASTER) && + (cfg->ll_bclk_role & GB_I2S_MGMT_ROLE_MASTER) && + (cfg->ll_wclk_role & GB_I2S_MGMT_ROLE_MASTER) && + (cfg->ll_wclk_polarity & GB_I2S_MGMT_POLARITY_NORMAL) && + (cfg->ll_wclk_change_edge & GB_I2S_MGMT_EDGE_FALLING) && + (cfg->ll_wclk_tx_edge & GB_I2S_MGMT_EDGE_FALLING) && + (cfg->ll_wclk_rx_edge & GB_I2S_MGMT_EDGE_RISING) && + (cfg->ll_data_offset == 1)) + break; + } + + if (i >= CONFIG_COUNT_MAX) { + pr_err("No valid configuration\n"); + ret = -EINVAL; + goto free_get_cfg; + } + + memcpy(&set_cfg, cfg, sizeof(set_cfg)); + set_cfg.config.byte_order = GB_I2S_MGMT_BYTE_ORDER_LE; + set_cfg.config.ll_protocol = GB_I2S_MGMT_PROTOCOL_I2S; + set_cfg.config.ll_mclk_role = GB_I2S_MGMT_ROLE_MASTER; + set_cfg.config.ll_bclk_role = GB_I2S_MGMT_ROLE_MASTER; + set_cfg.config.ll_wclk_role = GB_I2S_MGMT_ROLE_MASTER; + set_cfg.config.ll_wclk_polarity = GB_I2S_MGMT_POLARITY_NORMAL; + set_cfg.config.ll_wclk_change_edge = GB_I2S_MGMT_EDGE_RISING; + set_cfg.config.ll_wclk_tx_edge = GB_I2S_MGMT_EDGE_FALLING; + set_cfg.config.ll_wclk_rx_edge = GB_I2S_MGMT_EDGE_RISING; + + ret = gb_i2s_mgmt_set_configuration(connection, &set_cfg); + if (ret) { + pr_err("set_configuration failed: %d\n", ret); + goto free_get_cfg; + } + + ret = gb_i2s_mgmt_set_samples_per_message(connection, + CONFIG_SAMPLES_PER_MSG); + if (ret) { + pr_err("set_samples_per_msg failed: %d\n", ret); + goto free_get_cfg; + } + + /* XXX Add start delay here (probably 1ms) */ + ret = gb_i2s_mgmt_activate_cport(connection, + CONFIG_I2S_REMOTE_DATA_CPORT); + if (ret) { + pr_err("activate_cport failed: %d\n", ret); + goto free_get_cfg; + } + +free_get_cfg: + kfree(get_cfg); + return ret; +} + +int gb_i2s_send_data(struct gb_connection *connection, + void *req_buf, void *source_addr, + size_t len, int sample_num) +{ + struct gb_i2s_send_data_request *gb_req; + int ret; + + gb_req = req_buf; + gb_req->sample_number = sample_num; + + memcpy((void *)&gb_req->data[0], source_addr, len); + + if (len < MAX_SEND_DATA_LEN) + for (; len < MAX_SEND_DATA_LEN; len++) + gb_req->data[len] = gb_req->data[len - SAMPLE_SIZE]; + + gb_req->size = len; + + ret = gb_operation_sync(connection, GB_I2S_DATA_TYPE_SEND_DATA, + (void *) gb_req, SEND_DATA_BUF_LEN, NULL, 0); + return ret; +} diff --git a/drivers/staging/greybus/audio-pcm.c b/drivers/staging/greybus/audio-pcm.c new file mode 100644 index 000000000000..28b5e1106855 --- /dev/null +++ b/drivers/staging/greybus/audio-pcm.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "greybus.h" +#include "gpbridge.h" +#include "audio.h" + +/* + * timer/workqueue logic for pushing pcm data. + * + * Since when we are playing audio, we don't get any + * status or feedback from the codec, we have to use a + * hrtimer to trigger sending data to the remote codec. + * However since the hrtimer runs in irq context, so we + * have to schedule a workqueue to actually send the + * greybus data. + */ + +static void gb_pcm_work(struct work_struct *work) +{ + struct gb_snd *snd_dev = container_of(work, struct gb_snd, work); + struct snd_pcm_substream *substream = snd_dev->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int stride, frames, oldptr; + int period_elapsed; + char *address; + long len; + + if (!snd_dev) + return; + + if (!atomic_read(&snd_dev->running)) + return; + + address = runtime->dma_area + snd_dev->hwptr_done; + + len = frames_to_bytes(runtime, + runtime->buffer_size) - snd_dev->hwptr_done; + len = min(len, MAX_SEND_DATA_LEN); + gb_i2s_send_data(snd_dev->i2s_tx_connection, snd_dev->send_data_req_buf, + address, len, snd_dev->send_data_sample_count); + + snd_dev->send_data_sample_count += CONFIG_SAMPLES_PER_MSG; + + stride = runtime->frame_bits >> 3; + frames = len/stride; + + snd_pcm_stream_lock(substream); + oldptr = snd_dev->hwptr_done; + snd_dev->hwptr_done += len; + if (snd_dev->hwptr_done >= runtime->buffer_size * stride) + snd_dev->hwptr_done -= runtime->buffer_size * stride; + + frames = (len + (oldptr % stride)) / stride; + + snd_dev->transfer_done += frames; + if (snd_dev->transfer_done >= runtime->period_size) { + snd_dev->transfer_done -= runtime->period_size; + period_elapsed = 1; + } + + snd_pcm_stream_unlock(substream); + if (period_elapsed) + snd_pcm_period_elapsed(snd_dev->substream); +} + +static enum hrtimer_restart gb_pcm_timer_function(struct hrtimer *hrtimer) +{ + struct gb_snd *snd_dev = container_of(hrtimer, struct gb_snd, timer); + + if (!atomic_read(&snd_dev->running)) + return HRTIMER_NORESTART; + queue_work(snd_dev->workqueue, &snd_dev->work); + hrtimer_forward_now(hrtimer, ns_to_ktime(CONFIG_PERIOD_NS)); + return HRTIMER_RESTART; +} + +void gb_pcm_hrtimer_start(struct gb_snd *snd_dev) +{ + atomic_set(&snd_dev->running, 1); + hrtimer_start(&snd_dev->timer, ns_to_ktime(CONFIG_PERIOD_NS), + HRTIMER_MODE_REL); +} + +void gb_pcm_hrtimer_stop(struct gb_snd *snd_dev) +{ + atomic_set(&snd_dev->running, 0); + hrtimer_cancel(&snd_dev->timer); +} + +static int gb_pcm_hrtimer_init(struct gb_snd *snd_dev) +{ + hrtimer_init(&snd_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + snd_dev->timer.function = gb_pcm_timer_function; + atomic_set(&snd_dev->running, 0); + snd_dev->workqueue = alloc_workqueue("gb-audio", WQ_HIGHPRI, 0); + if (!snd_dev->workqueue) + return -ENOMEM; + INIT_WORK(&snd_dev->work, gb_pcm_work); + return 0; +} + + +/* + * Core gb pcm structure + */ +static struct snd_pcm_hardware gb_plat_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = GB_FMTS, + .rates = GB_RATES, + .rate_min = 8000, + .rate_max = GB_SAMPLE_RATE, + .channels_min = 1, + .channels_max = 2, + /* XXX - All the values below are junk */ + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 2, + .periods_max = 32, +}; + +static snd_pcm_uframes_t gb_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct gb_snd *snd_dev; + + snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + return snd_dev->hwptr_done / (substream->runtime->frame_bits >> 3); +} + +static int gb_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct gb_snd *snd_dev; + + snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + snd_dev->hwptr_done = 0; + snd_dev->transfer_done = 0; + return 0; +} + +static unsigned int rates[] = {GB_SAMPLE_RATE}; +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int gb_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct gb_snd *snd_dev; + unsigned long flags; + int ret; + + snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + spin_lock_irqsave(&snd_dev->lock, flags); + runtime->private_data = snd_dev; + snd_dev->substream = substream; + ret = gb_pcm_hrtimer_init(snd_dev); + spin_unlock_irqrestore(&snd_dev->lock, flags); + + if (ret) + return ret; + + snd_soc_set_runtime_hwparams(substream, &gb_plat_pcm_hardware); + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (ret < 0) + return ret; + + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int gb_pcm_close(struct snd_pcm_substream *substream) +{ + substream->runtime->private_data = NULL; + return 0; +} + +static int gb_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int gb_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static struct snd_pcm_ops gb_pcm_ops = { + .open = gb_pcm_open, + .close = gb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = gb_pcm_hw_params, + .hw_free = gb_pcm_hw_free, + .prepare = gb_pcm_prepare, + .pointer = gb_pcm_pointer, +}; + +static void gb_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int gb_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + return snd_pcm_lib_preallocate_pages_for_all( + pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); +} + +struct snd_soc_platform_driver gb_soc_platform = { + .ops = &gb_pcm_ops, + .pcm_new = gb_pcm_new, + .pcm_free = gb_pcm_free, +}; + +static int gb_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &gb_soc_platform); +} + +static int gb_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +struct platform_driver gb_audio_pcm_driver = { + .driver = { + .name = "gb-pcm-audio", + .owner = THIS_MODULE, + }, + .probe = gb_soc_platform_probe, + .remove = gb_soc_platform_remove, +}; diff --git a/drivers/staging/greybus/audio.c b/drivers/staging/greybus/audio.c new file mode 100644 index 000000000000..717e00255ca6 --- /dev/null +++ b/drivers/staging/greybus/audio.c @@ -0,0 +1,432 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "greybus.h" +#include "gpbridge.h" +#include "audio.h" + + +#define GB_AUDIO_DATA_DRIVER_NAME "gb_audio_data" +#define GB_AUDIO_MGMT_DRIVER_NAME "gb_audio_mgmt" + +/* + * gb_snd management functions + */ +static DEFINE_SPINLOCK(gb_snd_list_lock); +static LIST_HEAD(gb_snd_list); +static int device_count; + +static struct gb_snd *gb_find_snd(int bundle_id) +{ + struct gb_snd *tmp, *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&gb_snd_list_lock, flags); + list_for_each_entry(tmp, &gb_snd_list, list) + if (tmp->gb_bundle_id == bundle_id) { + ret = tmp; + break; + } + spin_unlock_irqrestore(&gb_snd_list_lock, flags); + return ret; +} + +static struct gb_snd *gb_get_snd(int bundle_id) +{ + struct gb_snd *snd_dev; + unsigned long flags; + + snd_dev = gb_find_snd(bundle_id); + if (snd_dev) + return snd_dev; + + snd_dev = kzalloc(sizeof(*snd_dev), GFP_KERNEL); + if (!snd_dev) + return NULL; + + spin_lock_init(&snd_dev->lock); + snd_dev->device_count = device_count++; + snd_dev->gb_bundle_id = bundle_id; + spin_lock_irqsave(&gb_snd_list_lock, flags); + list_add(&snd_dev->list, &gb_snd_list); + spin_unlock_irqrestore(&gb_snd_list_lock, flags); + return snd_dev; +} + +static void gb_free_snd(struct gb_snd *snd) +{ + unsigned long flags; + + spin_lock_irqsave(&gb_snd_list_lock, flags); + if (!snd->i2s_tx_connection && + !snd->mgmt_connection) { + list_del(&snd->list); + spin_unlock_irqrestore(&gb_snd_list_lock, flags); + kfree(snd); + } else { + spin_unlock_irqrestore(&gb_snd_list_lock, flags); + } +} + + + + +/* + * This is the ASoC simple card binds the platform codec, + * cpu-dai and codec-dai togheter + */ +struct gb_card_info_object { + struct asoc_simple_card_info card_info; + char codec_name[255]; + char platform_name[255]; + char dai_name[255]; +}; + + +struct asoc_simple_card_info *setup_card_info(int device_count) +{ + struct gb_card_info_object *obj; + + obj = kzalloc(sizeof(struct gb_card_info_object), GFP_KERNEL); + if (!obj) + return NULL; + + obj->card_info.name = "Greybus Audio Module"; + obj->card_info.card = "gb-card"; + obj->card_info.codec = obj->codec_name; + obj->card_info.platform = obj->platform_name; + obj->card_info.cpu_dai.name = obj->dai_name; + obj->card_info.cpu_dai.fmt = GB_FMTS; +#if USE_RT5645 + obj->card_info.daifmt = GB_FMTS; + sprintf(obj->codec_name, "rt5645.%s", "6-001b"); /* XXX do i2c bus addr dynamically */ + obj->card_info.codec_dai.name = "rt5645-aif1"; + obj->card_info.codec_dai.fmt = SND_SOC_DAIFMT_CBM_CFM; + obj->card_info.codec_dai.sysclk = 12288000; +#else + sprintf(obj->codec_name, "spdif-dit"); + obj->card_info.codec_dai.name = "dit-hifi"; +#endif + sprintf(obj->platform_name, "gb-pcm-audio.%i", device_count); + sprintf(obj->dai_name, "gb-dai-audio.%i", device_count); + + return &obj->card_info; +} + +void free_card_info(struct asoc_simple_card_info *ci) +{ + struct gb_card_info_object *obj; + + obj = container_of(ci, struct gb_card_info_object, card_info); + kfree(obj); +} + + +/* + * XXX this is sort of cruddy but I get warnings if + * we don't have dev.release handler set. + */ +static void default_release(struct device *dev) +{ +} + +/* + * GB connection hooks + */ +static int gb_i2s_transmitter_connection_init(struct gb_connection *connection) +{ + struct gb_snd *snd_dev; + struct platform_device *codec, *dai; + struct asoc_simple_card_info *simple_card; + unsigned long flags; + int ret; + + snd_dev = gb_get_snd(connection->bundle->id); + if (!snd_dev) + return -ENOMEM; + + codec = platform_device_register_simple("spdif-dit", -1, NULL, 0); + if (!codec) { + ret = -ENOMEM; + goto out; + } + + dai = platform_device_register_simple("gb-pcm-audio", snd_dev->device_count, NULL, 0); + if (!dai) { + ret = -ENOMEM; + goto out; + } + + simple_card = setup_card_info(snd_dev->device_count); + if (!simple_card) { + ret = -ENOMEM; + goto out; + } + + spin_lock_irqsave(&snd_dev->lock, flags); + snd_dev->card.name = "asoc-simple-card"; + snd_dev->card.id = snd_dev->device_count; + snd_dev->card.dev.release = default_release; /* XXX - suspicious */ + + snd_dev->cpu_dai.name = "gb-dai-audio"; + snd_dev->cpu_dai.id = snd_dev->device_count; + snd_dev->cpu_dai.dev.release = default_release; /* XXX - suspicious */ + + + snd_dev->simple_card_info = simple_card; + snd_dev->card.dev.platform_data = simple_card; + + snd_dev->codec = codec; + snd_dev->i2s_tx_connection = connection; + snd_dev->cpu_dai.dev.platform_data = snd_dev; + snd_dev->i2s_tx_connection->private = snd_dev; + spin_unlock_irqrestore(&snd_dev->lock, flags); + + ret = platform_device_register(&snd_dev->cpu_dai); + if (ret) { + pr_err("cpu_dai platform_device register failed\n"); + goto out_dai; + } + + ret = platform_device_register(&snd_dev->card); + if (ret) { + pr_err("card platform_device register failed\n"); + goto out_card; + } + + ret = gb_i2s_data_get_version(connection); + if (ret) { + pr_err("i2s data get_version() failed: %d\n", ret); + goto out_get_ver; + } + + return 0; + +out_get_ver: + platform_device_unregister(&snd_dev->card); +out_card: + platform_device_unregister(&snd_dev->cpu_dai); +out_dai: + platform_device_unregister(codec); +out: + gb_free_snd(snd_dev); + return ret; +} + +static void gb_i2s_transmitter_connection_exit(struct gb_connection *connection) +{ + struct gb_snd *snd_dev; + + snd_dev = (struct gb_snd *)connection->private; + + platform_device_unregister(&snd_dev->card); + platform_device_unregister(&snd_dev->cpu_dai); + platform_device_unregister(snd_dev->codec); + + free_card_info(snd_dev->simple_card_info); + snd_dev->i2s_tx_connection = NULL; + gb_free_snd(snd_dev); +} + +static int gb_i2s_mgmt_connection_init(struct gb_connection *connection) +{ + struct gb_snd *snd_dev; + unsigned long flags; + int ret; + + snd_dev = gb_get_snd(connection->bundle->id); + if (!snd_dev) + return -ENOMEM; + + spin_lock_irqsave(&snd_dev->lock, flags); + snd_dev->mgmt_connection = connection; + connection->private = snd_dev; + spin_unlock_irqrestore(&snd_dev->lock, flags); + + ret = gb_i2s_mgmt_get_version(connection); + if (ret) { + pr_err("i2s mgmt get_version() failed: %d\n", ret); + goto err_free_snd_dev; + } + + gb_i2s_mgmt_setup(connection); + + snd_dev->send_data_req_buf = kzalloc(SEND_DATA_BUF_LEN, GFP_KERNEL); + + if (!snd_dev->send_data_req_buf) { + ret = -ENOMEM; + goto err_deactivate_cport; + } + + return 0; + +err_deactivate_cport: + gb_i2s_mgmt_deactivate_cport(connection, CONFIG_I2S_REMOTE_DATA_CPORT); +err_free_snd_dev: + gb_free_snd(snd_dev); + return ret; +} + +static void gb_i2s_mgmt_connection_exit(struct gb_connection *connection) +{ + struct gb_snd *snd_dev = (struct gb_snd *)connection->private; + int ret; + + ret = gb_i2s_mgmt_deactivate_cport(connection, + CONFIG_I2S_REMOTE_DATA_CPORT); + if (ret) + pr_err("deactivate_cport failed: %d\n", ret); + + kfree(snd_dev->send_data_req_buf); + snd_dev->send_data_req_buf = NULL; + + snd_dev->mgmt_connection = NULL; + gb_free_snd(snd_dev); +} + +static int gb_i2s_mgmt_report_event_recv(u8 type, struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_i2s_mgmt_report_event_request *req = op->request->payload; + char *event_name; + + if (type != GB_I2S_MGMT_TYPE_REPORT_EVENT) { + dev_err(&connection->dev, "Invalid request type: %d\n", + type); + return -EINVAL; + } + + if (op->request->payload_size < sizeof(*req)) { + dev_err(&connection->dev, "Short request received: %d, %d\n", + op->request->payload_size, sizeof(*req)); + return -EINVAL; + } + + switch (req->event) { + case GB_I2S_MGMT_EVENT_UNSPECIFIED: + event_name = "UNSPECIFIED"; + break; + case GB_I2S_MGMT_EVENT_HALT: + /* XXX Should stop streaming now */ + event_name = "HALT"; + break; + case GB_I2S_MGMT_EVENT_INTERNAL_ERROR: + event_name = "INTERNAL_ERROR"; + break; + case GB_I2S_MGMT_EVENT_PROTOCOL_ERROR: + event_name = "PROTOCOL_ERROR"; + break; + case GB_I2S_MGMT_EVENT_FAILURE: + event_name = "FAILURE"; + break; + case GB_I2S_MGMT_EVENT_OUT_OF_SEQUENCE: + event_name = "OUT_OF_SEQUENCE"; + break; + case GB_I2S_MGMT_EVENT_UNDERRUN: + event_name = "UNDERRUN"; + break; + case GB_I2S_MGMT_EVENT_OVERRUN: + event_name = "OVERRUN"; + break; + case GB_I2S_MGMT_EVENT_CLOCKING: + event_name = "CLOCKING"; + break; + case GB_I2S_MGMT_EVENT_DATA_LEN: + event_name = "DATA_LEN"; + break; + default: + dev_warn(&connection->dev, "Unknown I2S Event received: %d\n", + req->event); + return -EINVAL; + } + + dev_warn(&connection->dev, "I2S Event received: %d - '%s'\n", + req->event, event_name); + + return 0; +} + +static struct gb_protocol gb_i2s_receiver_protocol = { + .name = GB_AUDIO_DATA_DRIVER_NAME, + .id = GREYBUS_PROTOCOL_I2S_RECEIVER, + .major = 0, + .minor = 1, + .connection_init = gb_i2s_transmitter_connection_init, + .connection_exit = gb_i2s_transmitter_connection_exit, + .request_recv = NULL, +}; + +static struct gb_protocol gb_i2s_mgmt_protocol = { + .name = GB_AUDIO_MGMT_DRIVER_NAME, + .id = GREYBUS_PROTOCOL_I2S_MGMT, + .major = 0, + .minor = 1, + .connection_init = gb_i2s_mgmt_connection_init, + .connection_exit = gb_i2s_mgmt_connection_exit, + .request_recv = gb_i2s_mgmt_report_event_recv, +}; + + +/* + * This is the basic hook get things initialized and registered w/ gb + */ + +int gb_audio_protocol_init(void) +{ + int err; + + err = gb_protocol_register(&gb_i2s_mgmt_protocol); + if (err) { + pr_err("Can't register i2s mgmt protocol driver: %d\n", -err); + return err; + } + + err = gb_protocol_register(&gb_i2s_receiver_protocol); + if (err) { + pr_err("Can't register Audio protocol driver: %d\n", -err); + goto err_unregister_i2s_mgmt; + } + + err = platform_driver_register(&gb_audio_plat_driver); + if (err) { + pr_err("Can't register platform driver: %d\n", -err); + goto err_unregister_plat; + } + + err = platform_driver_register(&gb_audio_pcm_driver); + if (err) { + pr_err("Can't register pcm driver: %d\n", -err); + goto err_unregister_pcm; + } + + return 0; + +err_unregister_pcm: + platform_driver_unregister(&gb_audio_plat_driver); +err_unregister_plat: + gb_protocol_deregister(&gb_i2s_receiver_protocol); +err_unregister_i2s_mgmt: + gb_protocol_deregister(&gb_i2s_mgmt_protocol); + return err; +} + +void gb_audio_protocol_exit(void) +{ + platform_driver_unregister(&gb_audio_pcm_driver); + platform_driver_unregister(&gb_audio_plat_driver); + gb_protocol_deregister(&gb_i2s_receiver_protocol); + gb_protocol_deregister(&gb_i2s_mgmt_protocol); +} + + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/greybus/audio.h b/drivers/staging/greybus/audio.h new file mode 100644 index 000000000000..6a337d112976 --- /dev/null +++ b/drivers/staging/greybus/audio.h @@ -0,0 +1,100 @@ +#ifndef __GB_AUDIO_H +#define __GB_AUDIO_H +#include +#include +#include +#include +#include +#include +#include "greybus.h" +#include "gpbridge.h" + + +#define GB_SAMPLE_RATE 48000 +#define GB_RATES SNDRV_PCM_RATE_48000 +#define GB_FMTS SNDRV_PCM_FMTBIT_S16_LE +#define PREALLOC_BUFFER (32 * 1024) +#define PREALLOC_BUFFER_MAX (32 * 1024) + +/* assuming 1 ms samples @ 48KHz */ +#define CONFIG_SAMPLES_PER_MSG 48L +#define CONFIG_PERIOD_NS 1000000 /* send msg every 1ms */ + +#define CONFIG_COUNT_MAX 32 +#define CONFIG_I2S_REMOTE_DATA_CPORT 7 /* XXX shouldn't be hardcoded...*/ +#define RT5647_SLAVE_ADDR 0x1b /* from toshiba/quanta code */ + +/* Switch between dummy spdif and jetson rt5645 codec */ +#define USE_RT5645 0 + +#define SAMPLE_SIZE 4 +#define MAX_SEND_DATA_LEN (CONFIG_SAMPLES_PER_MSG * SAMPLE_SIZE) +#define SEND_DATA_BUF_LEN (sizeof(struct gb_i2s_send_data_request) + \ + MAX_SEND_DATA_LEN) + + +/* + * This is the gb_snd structure which ties everything together + * and fakes DMA interrupts via a timer. + */ +struct gb_snd { + struct platform_device card; + struct platform_device cpu_dai; + struct platform_device *codec; + struct asoc_simple_card_info *simple_card_info; + struct gb_connection *mgmt_connection; + struct gb_connection *i2s_tx_connection; + struct gb_connection *i2s_rx_connection; + char *send_data_req_buf; + long send_data_sample_count; + int gb_bundle_id; + int device_count; + struct snd_pcm_substream *substream; + struct hrtimer timer; + atomic_t running; + struct workqueue_struct *workqueue; + struct work_struct work; + int hwptr_done; + int transfer_done; + struct list_head list; + spinlock_t lock; +}; + + +/* + * GB I2S cmd functions + */ +int gb_i2s_mgmt_get_version(struct gb_connection *connection); +int gb_i2s_data_get_version(struct gb_connection *connection); +int gb_i2s_mgmt_activate_cport(struct gb_connection *connection, + uint16_t cport); +int gb_i2s_mgmt_deactivate_cport(struct gb_connection *connection, + uint16_t cport); +int gb_i2s_mgmt_get_supported_configurations( + struct gb_connection *connection, + struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg, + size_t size); +int gb_i2s_mgmt_set_configuration(struct gb_connection *connection, + struct gb_i2s_mgmt_set_configuration_request *set_cfg); +int gb_i2s_mgmt_set_samples_per_message(struct gb_connection *connection, + uint16_t samples_per_message); +int gb_i2s_mgmt_setup(struct gb_connection *connection); +int gb_i2s_send_data(struct gb_connection *connection, void *req_buf, + void *source_addr, size_t len, int sample_num); + + +/* + * GB PCM hooks + */ +void gb_pcm_hrtimer_start(struct gb_snd *snd_dev); +void gb_pcm_hrtimer_stop(struct gb_snd *snd_dev); + + +/* + * Platform drivers + */ +extern struct platform_driver gb_audio_pcm_driver; +extern struct platform_driver gb_audio_plat_driver; + + +#endif /* __GB_AUDIO_H */ diff --git a/drivers/staging/greybus/gpb.c b/drivers/staging/greybus/gpb.c index d5747b291171..2324270e5c55 100644 --- a/drivers/staging/greybus/gpb.c +++ b/drivers/staging/greybus/gpb.c @@ -53,8 +53,15 @@ static int __init gpbridge_init(void) pr_err("error initializing hid protocol\n"); goto error_hid; } + if (gb_audio_protocol_init()) { + pr_err("error initializing audio protocols\n"); + goto error_audio; + } + return 0; +error_audio: + gb_hid_protocol_exit(); error_hid: gb_spi_protocol_exit(); error_spi: @@ -76,6 +83,7 @@ module_init(gpbridge_init); static void __exit gpbridge_exit(void) { + gb_audio_protocol_exit(); gb_hid_protocol_exit(); gb_spi_protocol_exit(); gb_i2c_protocol_exit(); diff --git a/drivers/staging/greybus/gpbridge.h b/drivers/staging/greybus/gpbridge.h index 85d801960564..85cc38522967 100644 --- a/drivers/staging/greybus/gpbridge.h +++ b/drivers/staging/greybus/gpbridge.h @@ -341,6 +341,7 @@ struct gb_i2s_mgmt_configuration { __u8 pad; __le32 spatial_locations; __le32 ll_protocol; + __u8 ll_mclk_role; __u8 ll_bclk_role; __u8 ll_wclk_role; __u8 ll_wclk_polarity; @@ -348,7 +349,6 @@ struct gb_i2s_mgmt_configuration { __u8 ll_wclk_tx_edge; __u8 ll_wclk_rx_edge; __u8 ll_data_offset; - __u8 ll_pad; }; /* get supported configurations request has no payload */ diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h index 495d13aed365..7958802f2629 100644 --- a/drivers/staging/greybus/protocol.h +++ b/drivers/staging/greybus/protocol.h @@ -87,6 +87,9 @@ extern void gb_spi_protocol_exit(void); extern int gb_hid_protocol_init(void); extern void gb_hid_protocol_exit(void); +extern int gb_audio_protocol_init(void); +extern void gb_audio_protocol_exit(void); + #define gb_protocol_driver(__protocol) \ static int __init protocol_init(void) \ { \