diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index e5cae29d5a32..42804547ba24 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -25,6 +25,7 @@ gb-phy-y := gpbridge.o \ audio-gb-cmds.o # Prefix all modules with gb- +gb-svc-y := svc.o gb-vibrator-y := vibrator.o gb-battery-y := battery.o gb-loopback-y := loopback.o @@ -33,6 +34,7 @@ gb-es1-y := es1.o gb-es2-y := es2.o obj-m += greybus.o +obj-m += gb-svc.o obj-m += gb-phy.o obj-m += gb-vibrator.o obj-m += gb-battery.o diff --git a/drivers/staging/greybus/audio-dai.c b/drivers/staging/greybus/audio-dai.c index 39b72f0d2138..9b162bfc2d17 100644 --- a/drivers/staging/greybus/audio-dai.c +++ b/drivers/staging/greybus/audio-dai.c @@ -1,3 +1,12 @@ +/* + * Greybus audio Digital Audio Interface (DAI) driver + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + #include #include #include @@ -11,8 +20,8 @@ #include #include #include + #include "greybus.h" -#include "gpbridge.h" #include "audio.h" /* diff --git a/drivers/staging/greybus/audio-gb-cmds.c b/drivers/staging/greybus/audio-gb-cmds.c index daa3181c9422..9ca3164b1332 100644 --- a/drivers/staging/greybus/audio-gb-cmds.c +++ b/drivers/staging/greybus/audio-gb-cmds.c @@ -1,6 +1,15 @@ +/* + * Greybus audio commands + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + #include + #include "greybus.h" -#include "gpbridge.h" #include "audio.h" #define GB_I2S_MGMT_VERSION_MAJOR 0x00 @@ -89,21 +98,12 @@ int gb_i2s_mgmt_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) +int gb_i2s_mgmt_get_cfgs(struct gb_snd *snd_dev, + 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; + int ret; size = sizeof(*get_cfg) + (CONFIG_COUNT_MAX * sizeof(get_cfg->config[0])); @@ -116,70 +116,79 @@ int gb_i2s_mgmt_setup(struct gb_connection *connection) size); if (ret) { pr_err("get_supported_config failed: %d\n", ret); - goto free_get_cfg; + goto err_free_get_cfg; } - /* Pick 48KHz 16-bits/channel */ - for (i = 0, cfg = get_cfg->config; i < CONFIG_COUNT_MAX; i++, cfg++) { - if ((le32_to_cpu(cfg->sample_frequency) == GB_SAMPLE_RATE) && - (cfg->num_channels == 2) && - (cfg->bytes_per_channel == 2) && - (cfg->byte_order & GB_I2S_MGMT_BYTE_ORDER_LE) && - (le32_to_cpu(cfg->spatial_locations) == - (GB_I2S_MGMT_SPATIAL_LOCATION_FL | - GB_I2S_MGMT_SPATIAL_LOCATION_FR)) && - (le32_to_cpu(cfg->ll_protocol) & GB_I2S_MGMT_PROTOCOL_I2S) && + snd_dev->i2s_configs = get_cfg; + + return 0; + +err_free_get_cfg: + kfree(get_cfg); + return ret; +} + +void gb_i2s_mgmt_free_cfgs(struct gb_snd *snd_dev) +{ + kfree(snd_dev->i2s_configs); + snd_dev->i2s_configs = NULL; +} + +int gb_i2s_mgmt_set_cfg(struct gb_snd *snd_dev, int rate, int chans, + int bytes_per_chan, int is_le) +{ + struct gb_i2s_mgmt_set_configuration_request set_cfg; + struct gb_i2s_mgmt_configuration *cfg; + int i, ret; + u8 byte_order = GB_I2S_MGMT_BYTE_ORDER_NA; + + if (bytes_per_chan > 1) { + if (is_le) + byte_order = GB_I2S_MGMT_BYTE_ORDER_LE; + else + byte_order = GB_I2S_MGMT_BYTE_ORDER_BE; + } + + for (i = 0, cfg = snd_dev->i2s_configs->config; + i < CONFIG_COUNT_MAX; + i++, cfg++) { + if ((cfg->sample_frequency == cpu_to_le32(rate)) && + (cfg->num_channels == chans) && + (cfg->bytes_per_channel == bytes_per_chan) && + (cfg->byte_order & byte_order) && + (cfg->ll_protocol & + cpu_to_le32(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_wclk_tx_edge & GB_I2S_MGMT_EDGE_RISING) && + (cfg->ll_wclk_rx_edge & GB_I2S_MGMT_EDGE_FALLING) && (cfg->ll_data_offset == 1)) break; } if (i >= CONFIG_COUNT_MAX) { pr_err("No valid configuration\n"); - ret = -EINVAL; - goto free_get_cfg; + return -EINVAL; } memcpy(&set_cfg, cfg, sizeof(set_cfg)); - set_cfg.config.byte_order = GB_I2S_MGMT_BYTE_ORDER_LE; + set_cfg.config.byte_order = byte_order; set_cfg.config.ll_protocol = cpu_to_le32(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; + set_cfg.config.ll_wclk_change_edge = GB_I2S_MGMT_EDGE_FALLING; + set_cfg.config.ll_wclk_tx_edge = GB_I2S_MGMT_EDGE_RISING; + set_cfg.config.ll_wclk_rx_edge = GB_I2S_MGMT_EDGE_FALLING; - ret = gb_i2s_mgmt_set_configuration(connection, &set_cfg); - if (ret) { + ret = gb_i2s_mgmt_set_configuration(snd_dev->mgmt_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; } diff --git a/drivers/staging/greybus/audio-pcm.c b/drivers/staging/greybus/audio-pcm.c index 92436300b3eb..c1b6aa551ce1 100644 --- a/drivers/staging/greybus/audio-pcm.c +++ b/drivers/staging/greybus/audio-pcm.c @@ -1,3 +1,12 @@ +/* + * Greybus audio Pulse Code Modulation (PCM) driver + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + #include #include #include @@ -11,8 +20,8 @@ #include #include #include + #include "greybus.h" -#include "gpbridge.h" #include "audio.h" /* @@ -32,15 +41,33 @@ static void gb_pcm_work(struct work_struct *work) struct snd_pcm_substream *substream = snd_dev->substream; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int stride, frames, oldptr; - int period_elapsed; + int period_elapsed, ret; char *address; long len; if (!snd_dev) return; - if (!atomic_read(&snd_dev->running)) + if (!atomic_read(&snd_dev->running)) { + if (snd_dev->cport_active) { + ret = gb_i2s_mgmt_deactivate_cport( + snd_dev->mgmt_connection, + snd_dev->i2s_tx_connection->bundle_cport_id); + if (ret) /* XXX Do what else with failure? */ + pr_err("deactivate_cport failed: %d\n", ret); + + snd_dev->cport_active = false; + } + return; + } else if (!snd_dev->cport_active) { + ret = gb_i2s_mgmt_activate_cport(snd_dev->mgmt_connection, + snd_dev->i2s_tx_connection->bundle_cport_id); + if (ret) + pr_err("activate_cport failed: %d\n", ret); + + snd_dev->cport_active = true; + } address = runtime->dma_area + snd_dev->hwptr_done; @@ -88,6 +115,7 @@ static enum hrtimer_restart gb_pcm_timer_function(struct hrtimer *hrtimer) void gb_pcm_hrtimer_start(struct gb_snd *snd_dev) { atomic_set(&snd_dev->running, 1); + queue_work(snd_dev->workqueue, &snd_dev->work); /* Activates CPort */ hrtimer_start(&snd_dev->timer, ns_to_ktime(CONFIG_PERIOD_NS), HRTIMER_MODE_REL); } @@ -96,6 +124,7 @@ void gb_pcm_hrtimer_stop(struct gb_snd *snd_dev) { atomic_set(&snd_dev->running, 0); hrtimer_cancel(&snd_dev->timer); + queue_work(snd_dev->workqueue, &snd_dev->work); /* Deactivates CPort */ } static int gb_pcm_hrtimer_init(struct gb_snd *snd_dev) @@ -200,6 +229,21 @@ static int gb_pcm_close(struct snd_pcm_substream *substream) static int gb_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct gb_snd *snd_dev; + int rate, chans, bytes_per_chan, is_le, ret; + + snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + rate = params_rate(hw_params); + chans = params_channels(hw_params); + bytes_per_chan = snd_pcm_format_width(params_format(hw_params)) / 8; + is_le = snd_pcm_format_little_endian(params_format(hw_params)); + + ret = gb_i2s_mgmt_set_cfg(snd_dev, rate, chans, bytes_per_chan, is_le); + if (ret) + return ret; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } diff --git a/drivers/staging/greybus/audio.c b/drivers/staging/greybus/audio.c index 232ba94b5b88..57c738b1293f 100644 --- a/drivers/staging/greybus/audio.c +++ b/drivers/staging/greybus/audio.c @@ -1,24 +1,35 @@ +/* + * Greybus audio driver + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + #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" +#define RT5647_I2C_ADAPTER_NR 6 +#define RT5647_I2C_ADDR 0x1b + /* * gb_snd management functions */ @@ -107,13 +118,17 @@ static struct asoc_simple_card_info *setup_card_info(int device_count) obj->card_info.platform = obj->platform_name; obj->card_info.cpu_dai.name = obj->dai_name; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - obj->card_info.cpu_dai.fmt = GB_FMTS; + obj->card_info.cpu_dai.fmt = SND_SOC_DAIFMT_CBM_CFM; #endif #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.daifmt = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S; + sprintf(obj->codec_name, "rt5645.%d-%04x", RT5647_I2C_ADAPTER_NR, + RT5647_I2C_ADDR); obj->card_info.codec_dai.name = "rt5645-aif1"; - obj->card_info.codec_dai.fmt = SND_SOC_DAIFMT_CBM_CFM; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) + obj->card_info.codec_dai.fmt = SND_SOC_DAIFMT_CBS_CFS; +#endif obj->card_info.codec_dai.sysclk = 12288000; #else sprintf(obj->codec_name, "spdif-dit"); @@ -150,6 +165,9 @@ 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; +#if USE_RT5645 + struct i2c_board_info rt5647_info; +#endif unsigned long flags; int ret; @@ -212,6 +230,18 @@ static int gb_i2s_transmitter_connection_init(struct gb_connection *connection) goto out_get_ver; } +#if USE_RT5645 + rt5647_info.addr = RT5647_I2C_ADDR; + strlcpy(rt5647_info.type, "rt5647", I2C_NAME_SIZE); + + snd_dev->rt5647 = i2c_new_device(i2c_get_adapter(RT5647_I2C_ADAPTER_NR), + &rt5647_info); + if (!snd_dev->rt5647) { + pr_err("can't create rt5647 i2c device\n"); + goto out_get_ver; + } +#endif + return 0; out_get_ver: @@ -231,6 +261,10 @@ static void gb_i2s_transmitter_connection_exit(struct gb_connection *connection) snd_dev = (struct gb_snd *)connection->private; +#if USE_RT5645 + i2c_unregister_device(snd_dev->rt5647); +#endif + platform_device_unregister(&snd_dev->card); platform_device_unregister(&snd_dev->cpu_dai); platform_device_unregister(snd_dev->codec); @@ -261,19 +295,30 @@ static int gb_i2s_mgmt_connection_init(struct gb_connection *connection) goto err_free_snd_dev; } - gb_i2s_mgmt_setup(connection); + ret = gb_i2s_mgmt_get_cfgs(snd_dev, connection); + if (ret) { + pr_err("can't get i2s configurations: %d\n", ret); + goto err_free_snd_dev; + } + + ret = gb_i2s_mgmt_set_samples_per_message(snd_dev->mgmt_connection, + CONFIG_SAMPLES_PER_MSG); + if (ret) { + pr_err("set_samples_per_msg failed: %d\n", ret); + goto err_free_i2s_configs; + } 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; + goto err_free_i2s_configs; } return 0; -err_deactivate_cport: - gb_i2s_mgmt_deactivate_cport(connection, CONFIG_I2S_REMOTE_DATA_CPORT); +err_free_i2s_configs: + gb_i2s_mgmt_free_cfgs(snd_dev); err_free_snd_dev: gb_free_snd(snd_dev); return ret; @@ -282,12 +327,8 @@ err_free_snd_dev: 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); + gb_i2s_mgmt_free_cfgs(snd_dev); kfree(snd_dev->send_data_req_buf); snd_dev->send_data_req_buf = NULL; diff --git a/drivers/staging/greybus/audio.h b/drivers/staging/greybus/audio.h index 6a337d112976..da95c1b3cb1e 100644 --- a/drivers/staging/greybus/audio.h +++ b/drivers/staging/greybus/audio.h @@ -1,14 +1,23 @@ +/* + * Greybus audio + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + #ifndef __GB_AUDIO_H #define __GB_AUDIO_H #include #include #include #include +#include #include #include -#include "greybus.h" -#include "gpbridge.h" +#include "greybus.h" #define GB_SAMPLE_RATE 48000 #define GB_RATES SNDRV_PCM_RATE_48000 @@ -20,9 +29,7 @@ #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 */ +#define CONFIG_COUNT_MAX 20 /* Switch between dummy spdif and jetson rt5645 codec */ #define USE_RT5645 0 @@ -42,9 +49,12 @@ struct gb_snd { struct platform_device cpu_dai; struct platform_device *codec; struct asoc_simple_card_info *simple_card_info; + struct i2c_client *rt5647; struct gb_connection *mgmt_connection; struct gb_connection *i2s_tx_connection; struct gb_connection *i2s_rx_connection; + struct gb_i2s_mgmt_get_supported_configurations_response + *i2s_configs; char *send_data_req_buf; long send_data_sample_count; int gb_bundle_id; @@ -52,6 +62,7 @@ struct gb_snd { struct snd_pcm_substream *substream; struct hrtimer timer; atomic_t running; + bool cport_active; struct workqueue_struct *workqueue; struct work_struct work; int hwptr_done; @@ -78,7 +89,11 @@ 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_mgmt_get_cfgs(struct gb_snd *snd_dev, + struct gb_connection *connection); +void gb_i2s_mgmt_free_cfgs(struct gb_snd *snd_dev); +int gb_i2s_mgmt_set_cfg(struct gb_snd *snd_dev, int rate, int chans, + int bytes_per_chan, int is_le); int gb_i2s_send_data(struct gb_connection *connection, void *req_buf, void *source_addr, size_t len, int sample_num); diff --git a/drivers/staging/greybus/core.c b/drivers/staging/greybus/core.c index 91dcc5bb158c..0a24822ac4ab 100644 --- a/drivers/staging/greybus/core.c +++ b/drivers/staging/greybus/core.c @@ -1,8 +1,8 @@ /* * Greybus "Core" * - * Copyright 2014 Google Inc. - * Copyright 2014 Linaro Ltd. + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. * * Released under the GPLv2 only. */ @@ -185,7 +185,7 @@ struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver if ((!driver->message_send) || (!driver->message_cancel) || (!driver->submit_svc)) { pr_err("Must implement all greybus_host_driver callbacks!\n"); - return NULL; + return ERR_PTR(-EINVAL); } if (buffer_size_max < GB_OPERATION_MESSAGE_SIZE_MIN) { @@ -205,7 +205,7 @@ struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL); if (!hd) - return NULL; + return ERR_PTR(-ENOMEM); kref_init(&hd->kref); hd->parent = parent; @@ -215,16 +215,24 @@ struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver ida_init(&hd->cport_id_map); hd->buffer_size_max = buffer_size_max; - hd->endo = gb_endo_create(hd); - if (!hd->endo) { - greybus_remove_hd(hd); - return NULL; - } - return hd; } EXPORT_SYMBOL_GPL(greybus_create_hd); +int greybus_endo_setup(struct greybus_host_device *hd, u16 endo_id, + u8 ap_intf_id) +{ + struct gb_endo *endo; + + endo = gb_endo_create(hd, endo_id, ap_intf_id); + if (IS_ERR(endo)) + return PTR_ERR(endo); + hd->endo = endo; + + return 0; +} +EXPORT_SYMBOL_GPL(greybus_endo_setup); + void greybus_remove_hd(struct greybus_host_device *hd) { /* diff --git a/drivers/staging/greybus/endo.c b/drivers/staging/greybus/endo.c index 42f26f07b52e..1e8485b07640 100644 --- a/drivers/staging/greybus/endo.c +++ b/drivers/staging/greybus/endo.c @@ -1,8 +1,8 @@ /* * Greybus endo code * - * Copyright 2015 Google Inc. - * Copyright 2014 Linaro Ltd. + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. * * Released under the GPLv2 only. */ @@ -29,36 +29,82 @@ #define endo_back_left_ribs(id, ribs) (((id) >> (ribs)) & ENDO_BACK_SIDE_RIBS_MASK(ribs)) #define endo_back_right_ribs(id, ribs) ((id) & ENDO_BACK_SIDE_RIBS_MASK(ribs)) +/* + * An Endo has interface block positions on the front and back. + * Each has numeric ID, starting with 1 (interface 0 represents + * the SVC within the Endo itself). The maximum interface ID is the + * also the number of non-SVC interfaces possible on the endo. + * + * Total number of interfaces: + * - Front: 4 + * - Back left: max_ribs + 1 + * - Back right: max_ribs + 1 + */ +#define max_endo_interface_id(endo_layout) \ + (4 + ((endo_layout)->max_ribs + 1) * 2) + /* endo sysfs attributes */ -static ssize_t serial_number_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t svc_serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct gb_endo *endo = to_gb_endo(dev); - return sprintf(buf, "%s", &endo->svc.serial_number[0]); + return sprintf(buf, "%s", &endo->svc_info.serial_number[0]); } -static DEVICE_ATTR_RO(serial_number); +static DEVICE_ATTR_RO(svc_serial_number); -static ssize_t version_show(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t svc_version_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct gb_endo *endo = to_gb_endo(dev); - return sprintf(buf, "%s", &endo->svc.version[0]); + return sprintf(buf, "%s", &endo->svc_info.version[0]); } -static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_RO(svc_version); -static struct attribute *endo_attrs[] = { - &dev_attr_serial_number.attr, - &dev_attr_version.attr, +static struct attribute *svc_attrs[] = { + &dev_attr_svc_serial_number.attr, + &dev_attr_svc_version.attr, NULL, }; -static const struct attribute_group endo_group = { - .attrs = endo_attrs, + +static const struct attribute_group svc_group = { + .attrs = svc_attrs, .name = "SVC", }; + +static ssize_t endo_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gb_endo *endo = to_gb_endo(dev); + + return sprintf(buf, "0x%04x", endo->id); +} +static DEVICE_ATTR_RO(endo_id); + +static ssize_t ap_intf_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gb_endo *endo = to_gb_endo(dev); + + return sprintf(buf, "0x%02x", endo->ap_intf_id); +} +static DEVICE_ATTR_RO(ap_intf_id); + +static struct attribute *endo_attrs[] = { + &dev_attr_endo_id.attr, + &dev_attr_ap_intf_id.attr, + NULL, +}; + +static const struct attribute_group endo_group = { + .attrs = endo_attrs, + .name = "Endo", +}; + static const struct attribute_group *endo_groups[] = { &endo_group, + &svc_group, NULL, }; @@ -361,19 +407,12 @@ static int create_modules(struct gb_endo *endo) int prev_module_id = 0; int interface_id; int module_id; - int interfaces; + int max_id; - /* - * Total number of interfaces: - * - Front: 4 - * - Back: - * - Left: max_ribs + 1 - * - Right: max_ribs + 1 - */ - interfaces = 4 + (endo->layout.max_ribs + 1) * 2; + max_id = max_endo_interface_id(&endo->layout); /* Find module corresponding to each interface */ - for (interface_id = 1; interface_id <= interfaces; interface_id++) { + for (interface_id = 1; interface_id <= max_id; interface_id++) { module_id = endo_get_module_id(endo, interface_id); if (WARN_ON(!module_id)) @@ -409,8 +448,8 @@ static int gb_endo_register(struct greybus_host_device *hd, // FIXME // Get the version and serial number from the SVC, right now we are // using "fake" numbers. - strcpy(&endo->svc.serial_number[0], "042"); - strcpy(&endo->svc.version[0], "0.0"); + strcpy(&endo->svc_info.serial_number[0], "042"); + strcpy(&endo->svc_info.version[0], "0.0"); dev_set_name(&endo->dev, "endo-0x%04x", endo->id); retval = device_add(&endo->dev); @@ -423,24 +462,31 @@ static int gb_endo_register(struct greybus_host_device *hd, return retval; } -struct gb_endo *gb_endo_create(struct greybus_host_device *hd) +struct gb_endo *gb_endo_create(struct greybus_host_device *hd, u16 endo_id, + u8 ap_intf_id) { struct gb_endo *endo; int retval; - u16 endo_id = 0x4755; // FIXME - get endo "ID" from the SVC endo = kzalloc(sizeof(*endo), GFP_KERNEL); if (!endo) - return NULL; + return ERR_PTR(-ENOMEM); /* First check if the value supplied is a valid endo id */ - if (gb_endo_validate_id(hd, &endo->layout, endo_id)) + if (gb_endo_validate_id(hd, &endo->layout, endo_id)) { + retval = -EINVAL; goto free_endo; - + } + if (ap_intf_id > max_endo_interface_id(&endo->layout)) { + retval = -EINVAL; + goto free_endo; + } endo->id = endo_id; + endo->ap_intf_id = ap_intf_id; /* Register Endo device */ - if (gb_endo_register(hd, endo)) + retval = gb_endo_register(hd, endo); + if (retval) goto free_endo; /* Create modules/interfaces */ @@ -454,7 +500,8 @@ struct gb_endo *gb_endo_create(struct greybus_host_device *hd) free_endo: kfree(endo); - return NULL; + + return ERR_PTR(retval); } void gb_endo_remove(struct gb_endo *endo) diff --git a/drivers/staging/greybus/endo.h b/drivers/staging/greybus/endo.h index dd0526949737..e211fb7d53bc 100644 --- a/drivers/staging/greybus/endo.h +++ b/drivers/staging/greybus/endo.h @@ -2,6 +2,7 @@ * Greybus endo code * * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. * * Released under the GPLv2 only. */ @@ -10,7 +11,7 @@ #define __ENDO_H /* Greybus "public" definitions" */ -struct gb_svc { +struct gb_svc_info { u8 serial_number[10]; u8 version[10]; }; @@ -36,10 +37,11 @@ struct endo_layout { }; struct gb_endo { - struct endo_layout layout; struct device dev; - struct gb_svc svc; + struct endo_layout layout; + struct gb_svc_info svc_info; u16 id; + u8 ap_intf_id; }; #define to_gb_endo(d) container_of(d, struct gb_endo, dev) @@ -47,7 +49,8 @@ struct gb_endo { /* Greybus "private" definitions */ struct greybus_host_device; -struct gb_endo *gb_endo_create(struct greybus_host_device *hd); +struct gb_endo *gb_endo_create(struct greybus_host_device *hd, + u16 endo_id, u8 ap_intf_id); void gb_endo_remove(struct gb_endo *endo); u8 endo_get_module_id(struct gb_endo *endo, u8 interface_id); diff --git a/drivers/staging/greybus/es1.c b/drivers/staging/greybus/es1.c index e0fae26d8ba3..2acfe74fc7b7 100644 --- a/drivers/staging/greybus/es1.c +++ b/drivers/staging/greybus/es1.c @@ -1,8 +1,8 @@ /* - * Greybus "AP" USB driver + * Greybus "AP" USB driver for "ES1" controller chips * - * Copyright 2014 Google Inc. - * Copyright 2014 Linaro Ltd. + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. * * Released under the GPLv2 only. */ @@ -551,14 +551,16 @@ static int ap_probe(struct usb_interface *interface, bool bulk_out_found = false; int retval = -ENOMEM; int i; + u16 endo_id = 0x4755; // FIXME - get endo "ID" from the SVC + u8 ap_intf_id = 0x01; // FIXME - get endo "ID" from the SVC u8 svc_interval = 0; udev = usb_get_dev(interface_to_usbdev(interface)); hd = greybus_create_hd(&es1_driver, &udev->dev, ES1_GBUF_MSG_SIZE_MAX); - if (!hd) { + if (IS_ERR(hd)) { usb_put_dev(udev); - return -ENOMEM; + return PTR_ERR(hd); } es1 = hd_to_es1(hd); @@ -659,6 +661,17 @@ static int ap_probe(struct usb_interface *interface, gb_debugfs_get(), es1, &apb1_log_enable_fops); + /* + * XXX Soon this will be initiated later, with a combination + * XXX of a Control protocol probe operation and a + * XXX subsequent Control protocol connected operation for + * XXX the SVC connection. At that point we know we're + * XXX properly connected to an Endo. + */ + retval = greybus_endo_setup(hd, endo_id, ap_intf_id); + if (retval) + goto error; + return 0; error: ap_disconnect(interface); diff --git a/drivers/staging/greybus/es2.c b/drivers/staging/greybus/es2.c index 05aac3d7686b..4f676cf3c583 100644 --- a/drivers/staging/greybus/es2.c +++ b/drivers/staging/greybus/es2.c @@ -551,14 +551,16 @@ static int ap_probe(struct usb_interface *interface, bool bulk_out_found = false; int retval = -ENOMEM; int i; + u16 endo_id = 0x4755; // FIXME - get endo "ID" from the SVC + u8 ap_intf_id = 0x01; // FIXME - get endo "ID" from the SVC u8 svc_interval = 0; udev = usb_get_dev(interface_to_usbdev(interface)); hd = greybus_create_hd(&es1_driver, &udev->dev, ES1_GBUF_MSG_SIZE_MAX); - if (!hd) { + if (IS_ERR(hd)) { usb_put_dev(udev); - return -ENOMEM; + return PTR_ERR(hd); } es1 = hd_to_es1(hd); @@ -659,6 +661,17 @@ static int ap_probe(struct usb_interface *interface, gb_debugfs_get(), es1, &apb1_log_enable_fops); + /* + * XXX Soon this will be initiated later, with a combination + * XXX of a Control protocol probe operation and a + * XXX subsequent Control protocol connected operation for + * XXX the SVC connection. At that point we know we're + * XXX properly connected to an Endo. + */ + retval = greybus_endo_setup(hd, endo_id, ap_intf_id); + if (retval) + goto error; + return 0; error: ap_disconnect(interface); diff --git a/drivers/staging/greybus/gpio.c b/drivers/staging/greybus/gpio.c index 871f2d0558b0..6e5fe5b3db39 100644 --- a/drivers/staging/greybus/gpio.c +++ b/drivers/staging/greybus/gpio.c @@ -14,7 +14,6 @@ #include #include #include "greybus.h" -#include "gpbridge.h" struct gb_gpio_line { /* The following has to be an array of line_max entries */ diff --git a/drivers/staging/greybus/greybus.h b/drivers/staging/greybus/greybus.h index dbb4e78cf4c9..86b2f9c67d0b 100644 --- a/drivers/staging/greybus/greybus.h +++ b/drivers/staging/greybus/greybus.h @@ -1,8 +1,8 @@ /* * Greybus driver and device API * - * Copyright 2014 Google Inc. - * Copyright 2014 Linaro Ltd. + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. * * Released under the GPLv2 only. */ @@ -23,8 +23,10 @@ #include "kernel_ver.h" #include "greybus_id.h" #include "greybus_manifest.h" +#include "greybus_protocols.h" #include "manifest.h" #include "endo.h" +#include "svc.h" #include "module.h" #include "interface.h" #include "bundle.h" @@ -107,6 +109,8 @@ struct greybus_host_device { struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *hd, struct device *parent, size_t buffer_size_max); +int greybus_endo_setup(struct greybus_host_device *hd, u16 endo_id, + u8 ap_intf_id); void greybus_remove_hd(struct greybus_host_device *hd); struct greybus_driver { diff --git a/drivers/staging/greybus/greybus_manifest.h b/drivers/staging/greybus/greybus_manifest.h index 076d7114cb4e..d6175cec6c17 100644 --- a/drivers/staging/greybus/greybus_manifest.h +++ b/drivers/staging/greybus/greybus_manifest.h @@ -4,8 +4,8 @@ * See "Greybus Application Protocol" document (version 0.1) for * details on these values and structures. * - * Copyright 2014 Google Inc. - * Copyright 2014 Linaro Ltd. + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. * * Released under the GPLv2 and BSD licenses. */ @@ -42,6 +42,7 @@ enum greybus_protocol { GREYBUS_PROTOCOL_LOOPBACK = 0x11, GREYBUS_PROTOCOL_I2S_RECEIVER = 0x12, GREYBUS_PROTOCOL_I2S_TRANSMITTER = 0x13, + GREYBUS_PROTOCOL_SVC = 0x14, /* ... */ GREYBUS_PROTOCOL_RAW = 0xfe, GREYBUS_PROTOCOL_VENDOR = 0xff, diff --git a/drivers/staging/greybus/gpbridge.h b/drivers/staging/greybus/greybus_protocols.h similarity index 91% rename from drivers/staging/greybus/gpbridge.h rename to drivers/staging/greybus/greybus_protocols.h index c8208054d942..28c40a09a05b 100644 --- a/drivers/staging/greybus/gpbridge.h +++ b/drivers/staging/greybus/greybus_protocols.h @@ -489,4 +489,61 @@ struct gb_spi_transfer_response { __u8 data[0]; /* inbound data */ }; +/* Version of the Greybus SVC protocol we support */ +#define GB_SVC_VERSION_MAJOR 0x00 +#define GB_SVC_VERSION_MINOR 0x01 + +/* Greybus SVC request types */ +#define GB_SVC_TYPE_INVALID 0x00 +#define GB_SVC_TYPE_PROTOCOL_VERSION 0x01 +#define GB_SVC_TYPE_INTF_DEVICE_ID 0x02 +#define GB_SVC_TYPE_INTF_HOTPLUG 0x03 +#define GB_SVC_TYPE_INTF_HOT_UNPLUG 0x04 +#define GB_SVC_TYPE_INTF_RESET 0x05 +#define GB_SVC_TYPE_CONN_CREATE 0x06 +#define GB_SVC_TYPE_CONN_DESTROY 0x07 + +struct gb_svc_intf_device_id_request { + __u8 intf_id; + __u8 device_id; +}; +/* device id response has no payload */ + +struct gb_svc_intf_hotplug_request { + __u8 intf_id; + struct { + __le32 unipro_mfg_id; + __le32 unipro_prod_id; + __le32 ara_vend_id; + __le32 ara_prod_id; + } data; +}; +/* hotplug response has no payload */ + +struct gb_svc_intf_hot_unplug_request { + __u8 intf_id; +}; +/* hot unplug response has no payload */ + +struct gb_svc_intf_reset_request { + __u8 intf_id; +}; +/* interface reset response has no payload */ + +struct gb_svc_conn_create_request { + __u8 intf1_id; + __u16 cport1_id; + __u8 intf2_id; + __u16 cport2_id; +}; +/* connection create response has no payload */ + +struct gb_svc_conn_destroy_request { + __u8 intf1_id; + __u16 cport1_id; + __u8 intf2_id; + __u16 cport2_id; +}; +/* connection destroy response has no payload */ + #endif /* __GB_GPBRIDGE_H__ */ diff --git a/drivers/staging/greybus/i2c.c b/drivers/staging/greybus/i2c.c index 14fdea184c39..01afca8408a0 100644 --- a/drivers/staging/greybus/i2c.c +++ b/drivers/staging/greybus/i2c.c @@ -13,8 +13,6 @@ #include #include "greybus.h" -#include "gpbridge.h" - struct gb_i2c_device { struct gb_connection *connection; diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h index 8bc3f769c398..f6739f3332cd 100644 --- a/drivers/staging/greybus/protocol.h +++ b/drivers/staging/greybus/protocol.h @@ -108,7 +108,7 @@ int __init gb_##__protocol##_init(void) \ { \ return gb_protocol_register(&__protocol); \ } \ -void __exit gb_##__protocol##_exit(void) \ +void gb_##__protocol##_exit(void) \ { \ gb_protocol_deregister(&__protocol); \ } \ diff --git a/drivers/staging/greybus/pwm.c b/drivers/staging/greybus/pwm.c index 7f675bfed1d7..5dfeb0e761c1 100644 --- a/drivers/staging/greybus/pwm.c +++ b/drivers/staging/greybus/pwm.c @@ -11,8 +11,8 @@ #include #include #include + #include "greybus.h" -#include "gpbridge.h" struct gb_pwm_chip { struct gb_connection *connection; diff --git a/drivers/staging/greybus/spi.c b/drivers/staging/greybus/spi.c index 430c3ad70a5a..78a7f85a4bf6 100644 --- a/drivers/staging/greybus/spi.c +++ b/drivers/staging/greybus/spi.c @@ -14,7 +14,6 @@ #include #include "greybus.h" -#include "gpbridge.h" struct gb_spi { struct gb_connection *connection; diff --git a/drivers/staging/greybus/svc.c b/drivers/staging/greybus/svc.c new file mode 100644 index 000000000000..e39eddbbcb9e --- /dev/null +++ b/drivers/staging/greybus/svc.c @@ -0,0 +1,256 @@ +/* + * SVC Greybus driver. + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include + +#include "greybus.h" +#include "greybus_protocols.h" + +struct gb_svc { + struct gb_connection *connection; + u8 version_major; + u8 version_minor; +}; + +/* Define get_version() routine */ +define_get_version(gb_svc, SVC); + +static int intf_device_id_operation(struct gb_svc *svc, + u8 intf_id, u8 device_id) +{ + struct gb_svc_intf_device_id_request request; + + request.intf_id = intf_id; + request.device_id = device_id; + + return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_DEVICE_ID, + &request, sizeof(request), NULL, 0); +} + +static int intf_reset_operation(struct gb_svc *svc, u8 intf_id) +{ + struct gb_svc_intf_reset_request request; + + request.intf_id = intf_id; + + return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_RESET, + &request, sizeof(request), NULL, 0); +} + +static int connection_create_operation(struct gb_svc *svc, + u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id) +{ + struct gb_svc_conn_create_request request; + + request.intf1_id = intf1_id; + request.cport1_id = cport1_id; + request.intf2_id = intf2_id; + request.cport2_id = cport2_id; + + return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_CREATE, + &request, sizeof(request), NULL, 0); +} + +static int connection_destroy_operation(struct gb_svc *svc, + u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id) +{ + struct gb_svc_conn_destroy_request request; + + request.intf1_id = intf1_id; + request.cport1_id = cport1_id; + request.intf2_id = intf2_id; + request.cport2_id = cport2_id; + + return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_DESTROY, + &request, sizeof(request), NULL, 0); +} + +int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id) +{ + return intf_device_id_operation(svc, intf_id, device_id); +} +EXPORT_SYMBOL_GPL(gb_svc_intf_device_id); + +int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id) +{ + return intf_reset_operation(svc, intf_id); +} +EXPORT_SYMBOL_GPL(gb_svc_intf_reset); + +int gb_svc_connection_create(struct gb_svc *svc, + u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id) +{ + return connection_create_operation(svc, intf1_id, cport1_id, + intf2_id, cport2_id); +} +EXPORT_SYMBOL_GPL(gb_svc_connection_create); + +int gb_svc_connection_destroy(struct gb_svc *svc, + u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id) +{ + return connection_destroy_operation(svc, intf1_id, cport1_id, + intf2_id, cport2_id); +} +EXPORT_SYMBOL_GPL(gb_svc_connection_destroy); + +static int gb_svc_intf_hotplug_recv(struct gb_operation *op) +{ + struct gb_message *request = op->request; + struct gb_svc_intf_hotplug_request *hotplug; + u8 intf_id; + u32 unipro_mfg_id; + u32 unipro_prod_id; + u32 ara_vend_id; + u32 ara_prod_id; + + if (request->payload_size < sizeof(*hotplug)) { + dev_err(&op->connection->dev, + "short hotplug request received\n"); + return -EINVAL; + } + hotplug = request->payload; + + /* + * Grab the information we need. + * + * XXX I'd really like to acknowledge receipt, and then + * XXX continue processing the request. There's no need + * XXX for the SVC to wait. In fact, it might be best to + * XXX have the SVC get acknowledgement before we proceed. + * */ + intf_id = hotplug->intf_id; + unipro_mfg_id = hotplug->data.unipro_mfg_id; + unipro_prod_id = hotplug->data.unipro_prod_id; + ara_vend_id = hotplug->data.ara_vend_id; + ara_prod_id = hotplug->data.ara_prod_id; + + /* FIXME Set up the interface here; may required firmware download */ + + return 0; +} + +static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op) +{ + struct gb_message *request = op->request; + struct gb_svc_intf_hot_unplug_request *hot_unplug; + u8 intf_id; + + if (request->payload_size < sizeof(*hot_unplug)) { + dev_err(&op->connection->dev, + "short hot unplug request received\n"); + return -EINVAL; + } + hot_unplug = request->payload; + + intf_id = hot_unplug->intf_id; + + /* FIXME Tear down the interface here */ + + return 0; + +} + +static int gb_svc_intf_reset_recv(struct gb_operation *op) +{ + struct gb_message *request = op->request; + struct gb_svc_intf_reset_request *reset; + u8 intf_id; + + if (request->payload_size < sizeof(*reset)) { + dev_err(&op->connection->dev, + "short reset request received\n"); + return -EINVAL; + } + reset = request->payload; + + intf_id = reset->intf_id; + + /* FIXME Reset the interface here */ + + return 0; +} + +static int gb_svc_request_recv(u8 type, struct gb_operation *op) +{ + switch (type) { + case GB_SVC_TYPE_INTF_HOTPLUG: + return gb_svc_intf_hotplug_recv(op); + case GB_SVC_TYPE_INTF_HOT_UNPLUG: + return gb_svc_intf_hot_unplug_recv(op); + case GB_SVC_TYPE_INTF_RESET: + return gb_svc_intf_reset_recv(op); + default: + dev_err(&op->connection->dev, + "unsupported request: %hhu\n", type); + return -EINVAL; + } +} + +/* + * Do initial setup of the SVC. + */ +static int gb_svc_device_setup(struct gb_svc *gb_svc) +{ + /* First thing we need to do is check the version */ + return get_version(gb_svc); +} + +static int gb_svc_connection_init(struct gb_connection *connection) +{ + struct gb_svc *svc; + int ret; + + svc = kzalloc(sizeof(*svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->connection = connection; + connection->private = svc; + ret = gb_svc_device_setup(svc); + if (ret) + kfree(svc); + + return ret; +} + +static void gb_svc_connection_exit(struct gb_connection *connection) +{ + struct gb_svc *svc = connection->private; + + if (!svc) + return; + + kfree(svc); +} + +static struct gb_protocol svc_protocol = { + .name = "svc", + .id = GREYBUS_PROTOCOL_SVC, + .major = 0, + .minor = 1, + .connection_init = gb_svc_connection_init, + .connection_exit = gb_svc_connection_exit, + .request_recv = gb_svc_request_recv, +}; + +int gb_svc_protocol_init(void) +{ + return gb_protocol_register(&svc_protocol); +} + +void gb_svc_protocol_exit(void) +{ + gb_protocol_deregister(&svc_protocol); +} diff --git a/drivers/staging/greybus/svc.h b/drivers/staging/greybus/svc.h new file mode 100644 index 000000000000..e26e63707e3d --- /dev/null +++ b/drivers/staging/greybus/svc.h @@ -0,0 +1,22 @@ +/* + * Greybus SVC code + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#ifndef __SVC_H +#define __SVC_H + +struct gb_svc; + +int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id); +int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id); +int gb_svc_connection_create(struct gb_svc *svc, u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id); +int gb_svc_connection_destroy(struct gb_svc *svc, u8 intf1_id, u16 cport1_id, + u8 intf2_id, u16 cport2_id); + +#endif /* __SVC_H */