soundwire: add Slave sysfs support

Expose MIPI DisCo Slave properties in sysfs.

For Slave properties and Data Port 0, the attributes are managed with
simple devm_ support.

A Slave Device may have more than one Data Port (DPN), and each Data
Port can be sink or source. The attributes are created dynamically
using pre-canned macros, but still use devm_ with a name attribute
group to avoid creating kobjects - as requested by GregKH. In the
_show function, we use container_of() to retrieve port number and
direction required to extract the information.

Audio modes are not supported for now. Depending on the discussions
the SoundWire Device Class, we may add it later as is or follow the
new specification.

Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://lore.kernel.org/r/20200518203551.2053-4-yung-chuan.liao@linux.intel.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Pierre-Louis Bossart 2020-05-19 04:35:51 +08:00 коммит произвёл Vinod Koul
Родитель c5778ca49a
Коммит bcac590299
8 изменённых файлов: 631 добавлений и 3 удалений

Просмотреть файл

@ -0,0 +1,91 @@
What: /sys/bus/soundwire/devices/sdw:.../dev-properties/mipi_revision
/sys/bus/soundwire/devices/sdw:.../dev-properties/wake_capable
/sys/bus/soundwire/devices/sdw:.../dev-properties/test_mode_capable
/sys/bus/soundwire/devices/sdw:.../dev-properties/clk_stop_mode1
/sys/bus/soundwire/devices/sdw:.../dev-properties/simple_clk_stop_capable
/sys/bus/soundwire/devices/sdw:.../dev-properties/clk_stop_timeout
/sys/bus/soundwire/devices/sdw:.../dev-properties/ch_prep_timeout
/sys/bus/soundwire/devices/sdw:.../dev-properties/reset_behave
/sys/bus/soundwire/devices/sdw:.../dev-properties/high_PHY_capable
/sys/bus/soundwire/devices/sdw:.../dev-properties/paging_support
/sys/bus/soundwire/devices/sdw:.../dev-properties/bank_delay_support
/sys/bus/soundwire/devices/sdw:.../dev-properties/p15_behave
/sys/bus/soundwire/devices/sdw:.../dev-properties/master_count
/sys/bus/soundwire/devices/sdw:.../dev-properties/source_ports
/sys/bus/soundwire/devices/sdw:.../dev-properties/sink_ports
Date: May 2020
Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Bard Liao <yung-chuan.liao@linux.intel.com>
Vinod Koul <vkoul@kernel.org>
Description: SoundWire Slave DisCo properties.
These properties are defined by MIPI DisCo Specification
for SoundWire. They define various properties of the
SoundWire Slave and are used by the bus to configure
the Slave
What: /sys/bus/soundwire/devices/sdw:.../dp0/max_word
/sys/bus/soundwire/devices/sdw:.../dp0/min_word
/sys/bus/soundwire/devices/sdw:.../dp0/words
/sys/bus/soundwire/devices/sdw:.../dp0/BRA_flow_controlled
/sys/bus/soundwire/devices/sdw:.../dp0/simple_ch_prep_sm
/sys/bus/soundwire/devices/sdw:.../dp0/imp_def_interrupts
Date: May 2020
Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Bard Liao <yung-chuan.liao@linux.intel.com>
Vinod Koul <vkoul@kernel.org>
Description: SoundWire Slave Data Port-0 DisCo properties.
These properties are defined by MIPI DisCo Specification
for the SoundWire. They define various properties of the
Data port 0 are used by the bus to configure the Data Port 0.
What: /sys/bus/soundwire/devices/sdw:.../dpN_src/max_word
/sys/bus/soundwire/devices/sdw:.../dpN_src/min_word
/sys/bus/soundwire/devices/sdw:.../dpN_src/words
/sys/bus/soundwire/devices/sdw:.../dpN_src/type
/sys/bus/soundwire/devices/sdw:.../dpN_src/max_grouping
/sys/bus/soundwire/devices/sdw:.../dpN_src/simple_ch_prep_sm
/sys/bus/soundwire/devices/sdw:.../dpN_src/ch_prep_timeout
/sys/bus/soundwire/devices/sdw:.../dpN_src/imp_def_interrupts
/sys/bus/soundwire/devices/sdw:.../dpN_src/min_ch
/sys/bus/soundwire/devices/sdw:.../dpN_src/max_ch
/sys/bus/soundwire/devices/sdw:.../dpN_src/channels
/sys/bus/soundwire/devices/sdw:.../dpN_src/ch_combinations
/sys/bus/soundwire/devices/sdw:.../dpN_src/max_async_buffer
/sys/bus/soundwire/devices/sdw:.../dpN_src/block_pack_mode
/sys/bus/soundwire/devices/sdw:.../dpN_src/port_encoding
/sys/bus/soundwire/devices/sdw:.../dpN_sink/max_word
/sys/bus/soundwire/devices/sdw:.../dpN_sink/min_word
/sys/bus/soundwire/devices/sdw:.../dpN_sink/words
/sys/bus/soundwire/devices/sdw:.../dpN_sink/type
/sys/bus/soundwire/devices/sdw:.../dpN_sink/max_grouping
/sys/bus/soundwire/devices/sdw:.../dpN_sink/simple_ch_prep_sm
/sys/bus/soundwire/devices/sdw:.../dpN_sink/ch_prep_timeout
/sys/bus/soundwire/devices/sdw:.../dpN_sink/imp_def_interrupts
/sys/bus/soundwire/devices/sdw:.../dpN_sink/min_ch
/sys/bus/soundwire/devices/sdw:.../dpN_sink/max_ch
/sys/bus/soundwire/devices/sdw:.../dpN_sink/channels
/sys/bus/soundwire/devices/sdw:.../dpN_sink/ch_combinations
/sys/bus/soundwire/devices/sdw:.../dpN_sink/max_async_buffer
/sys/bus/soundwire/devices/sdw:.../dpN_sink/block_pack_mode
/sys/bus/soundwire/devices/sdw:.../dpN_sink/port_encoding
Date: May 2020
Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Bard Liao <yung-chuan.liao@linux.intel.com>
Vinod Koul <vkoul@kernel.org>
Description: SoundWire Slave Data Source/Sink Port-N DisCo properties.
These properties are defined by MIPI DisCo Specification
for SoundWire. They define various properties of the
Source/Sink Data port N and are used by the bus to configure
the Data Port N.

Просмотреть файл

@ -4,7 +4,8 @@
#
#Bus Objs
soundwire-bus-objs := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o
soundwire-bus-objs := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o \
sysfs_slave.o sysfs_slave_dpn.o
obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o
ifdef CONFIG_DEBUG_FS

Просмотреть файл

@ -8,6 +8,7 @@
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include "bus.h"
#include "sysfs_local.h"
static DEFINE_IDA(sdw_ida);

Просмотреть файл

@ -175,5 +175,6 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0)
void sdw_clear_slave_status(struct sdw_bus *bus, u32 request);
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
#endif /* __SDW_BUS_H */

Просмотреть файл

@ -7,6 +7,7 @@
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "sysfs_local.h"
/**
* sdw_get_device_id - find the matching SoundWire device id
@ -46,8 +47,7 @@ static int sdw_bus_match(struct device *dev, struct device_driver *ddrv)
return ret;
}
static int sdw_slave_modalias(const struct sdw_slave *slave, char *buf,
size_t size)
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
{
/* modalias is sdw:m<mfg_id>p<part_id> */
@ -105,6 +105,11 @@ static int sdw_drv_probe(struct device *dev)
if (slave->ops && slave->ops->read_prop)
slave->ops->read_prop(slave);
/* init the sysfs as we have properties now */
ret = sdw_slave_sysfs_init(slave);
if (ret < 0)
dev_warn(dev, "Slave sysfs init failed:%d\n", ret);
/*
* Check for valid clk_stop_timeout, use DisCo worst case value of
* 300ms

Просмотреть файл

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2015-2020 Intel Corporation. */
#ifndef __SDW_SYSFS_LOCAL_H
#define __SDW_SYSFS_LOCAL_H
/*
* SDW sysfs APIs -
*/
int sdw_slave_sysfs_init(struct sdw_slave *slave);
int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave);
#endif /* __SDW_SYSFS_LOCAL_H */

Просмотреть файл

@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2015-2020 Intel Corporation.
#include <linux/device.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "sysfs_local.h"
/*
* Slave sysfs
*/
/*
* The sysfs for Slave reflects the MIPI description as given
* in the MIPI DisCo spec
*
* Base file is device
* |---- modalias
* |---- dev-properties
* |---- mipi_revision
* |---- wake_capable
* |---- test_mode_capable
* |---- clk_stop_mode1
* |---- simple_clk_stop_capable
* |---- clk_stop_timeout
* |---- ch_prep_timeout
* |---- reset_behave
* |---- high_PHY_capable
* |---- paging_support
* |---- bank_delay_support
* |---- p15_behave
* |---- master_count
* |---- source_ports
* |---- sink_ports
* |---- dp0
* |---- max_word
* |---- min_word
* |---- words
* |---- BRA_flow_controlled
* |---- simple_ch_prep_sm
* |---- imp_def_interrupts
* |---- dpN_<sink/src>
* |---- max_word
* |---- min_word
* |---- words
* |---- type
* |---- max_grouping
* |---- simple_ch_prep_sm
* |---- ch_prep_timeout
* |---- imp_def_interrupts
* |---- min_ch
* |---- max_ch
* |---- channels
* |---- ch_combinations
* |---- max_async_buffer
* |---- block_pack_mode
* |---- port_encoding
*
*/
#define sdw_slave_attr(field, format_string) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
return sprintf(buf, format_string, slave->prop.field); \
} \
static DEVICE_ATTR_RO(field)
sdw_slave_attr(mipi_revision, "0x%x\n");
sdw_slave_attr(wake_capable, "%d\n");
sdw_slave_attr(test_mode_capable, "%d\n");
sdw_slave_attr(clk_stop_mode1, "%d\n");
sdw_slave_attr(simple_clk_stop_capable, "%d\n");
sdw_slave_attr(clk_stop_timeout, "%d\n");
sdw_slave_attr(ch_prep_timeout, "%d\n");
sdw_slave_attr(reset_behave, "%d\n");
sdw_slave_attr(high_PHY_capable, "%d\n");
sdw_slave_attr(paging_support, "%d\n");
sdw_slave_attr(bank_delay_support, "%d\n");
sdw_slave_attr(p15_behave, "%d\n");
sdw_slave_attr(master_count, "%d\n");
sdw_slave_attr(source_ports, "0x%x\n");
sdw_slave_attr(sink_ports, "0x%x\n");
static ssize_t modalias_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
return sdw_slave_modalias(slave, buf, 256);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *slave_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(slave);
static struct attribute *slave_dev_attrs[] = {
&dev_attr_mipi_revision.attr,
&dev_attr_wake_capable.attr,
&dev_attr_test_mode_capable.attr,
&dev_attr_clk_stop_mode1.attr,
&dev_attr_simple_clk_stop_capable.attr,
&dev_attr_clk_stop_timeout.attr,
&dev_attr_ch_prep_timeout.attr,
&dev_attr_reset_behave.attr,
&dev_attr_high_PHY_capable.attr,
&dev_attr_paging_support.attr,
&dev_attr_bank_delay_support.attr,
&dev_attr_p15_behave.attr,
&dev_attr_master_count.attr,
&dev_attr_source_ports.attr,
&dev_attr_sink_ports.attr,
NULL,
};
/*
* we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
* for device-level properties
*/
static struct attribute_group sdw_slave_dev_attr_group = {
.attrs = slave_dev_attrs,
.name = "dev-properties",
};
/*
* DP0 sysfs
*/
#define sdw_dp0_attr(field, format_string) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
return sprintf(buf, format_string, slave->prop.dp0_prop->field);\
} \
static DEVICE_ATTR_RO(field)
sdw_dp0_attr(max_word, "%d\n");
sdw_dp0_attr(min_word, "%d\n");
sdw_dp0_attr(BRA_flow_controlled, "%d\n");
sdw_dp0_attr(simple_ch_prep_sm, "%d\n");
sdw_dp0_attr(imp_def_interrupts, "0x%x\n");
static ssize_t words_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
ssize_t size = 0;
int i;
for (i = 0; i < slave->prop.dp0_prop->num_words; i++)
size += sprintf(buf + size, "%d ",
slave->prop.dp0_prop->words[i]);
size += sprintf(buf + size, "\n");
return size;
}
static DEVICE_ATTR_RO(words);
static struct attribute *dp0_attrs[] = {
&dev_attr_max_word.attr,
&dev_attr_min_word.attr,
&dev_attr_words.attr,
&dev_attr_BRA_flow_controlled.attr,
&dev_attr_simple_ch_prep_sm.attr,
&dev_attr_imp_def_interrupts.attr,
NULL,
};
/*
* we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
* for dp0-level properties
*/
static const struct attribute_group dp0_group = {
.attrs = dp0_attrs,
.name = "dp0",
};
int sdw_slave_sysfs_init(struct sdw_slave *slave)
{
int ret;
ret = devm_device_add_groups(&slave->dev, slave_groups);
if (ret < 0)
return ret;
ret = devm_device_add_group(&slave->dev, &sdw_slave_dev_attr_group);
if (ret < 0)
return ret;
if (slave->prop.dp0_prop) {
ret = devm_device_add_group(&slave->dev, &dp0_group);
if (ret < 0)
return ret;
}
if (slave->prop.source_ports || slave->prop.sink_ports) {
ret = sdw_slave_sysfs_dpn_init(slave);
if (ret < 0)
return ret;
}
return 0;
}

Просмотреть файл

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2015-2020 Intel Corporation.
#include <linux/device.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "sysfs_local.h"
struct dpn_attribute {
struct device_attribute dev_attr;
int N;
int dir;
const char *format_string;
};
/*
* Since we can't use ARRAY_SIZE, hard-code number of dpN attributes.
* This needs to be updated when adding new attributes - an error will be
* flagged on a mismatch.
*/
#define SDW_DPN_ATTRIBUTES 15
#define sdw_dpn_attribute_alloc(field) \
static int field##_attribute_alloc(struct device *dev, \
struct attribute **res, \
int N, int dir, \
const char *format_string) \
{ \
struct dpn_attribute *dpn_attr; \
\
dpn_attr = devm_kzalloc(dev, sizeof(*dpn_attr), GFP_KERNEL); \
if (!dpn_attr) \
return -ENOMEM; \
dpn_attr->N = N; \
dpn_attr->dir = dir; \
dpn_attr->format_string = format_string; \
dpn_attr->dev_attr.attr.name = __stringify(field); \
dpn_attr->dev_attr.attr.mode = 0444; \
dpn_attr->dev_attr.show = field##_show; \
\
*res = &dpn_attr->dev_attr.attr; \
\
return 0; \
}
#define sdw_dpn_attr(field) \
\
static ssize_t field##_dpn_show(struct sdw_slave *slave, \
int N, \
int dir, \
const char *format_string, \
char *buf) \
{ \
struct sdw_dpn_prop *dpn; \
unsigned long mask; \
int bit; \
int i; \
\
if (dir) { \
dpn = slave->prop.src_dpn_prop; \
mask = slave->prop.source_ports; \
} else { \
dpn = slave->prop.sink_dpn_prop; \
mask = slave->prop.sink_ports; \
} \
\
i = 0; \
for_each_set_bit(bit, &mask, 32) { \
if (bit == N) { \
return sprintf(buf, format_string, \
dpn[i].field); \
} \
i++; \
} \
return -EINVAL; \
} \
\
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
struct dpn_attribute *dpn_attr = \
container_of(attr, struct dpn_attribute, dev_attr); \
\
return field##_dpn_show(slave, \
dpn_attr->N, dpn_attr->dir, \
dpn_attr->format_string, \
buf); \
} \
sdw_dpn_attribute_alloc(field)
sdw_dpn_attr(imp_def_interrupts);
sdw_dpn_attr(max_word);
sdw_dpn_attr(min_word);
sdw_dpn_attr(type);
sdw_dpn_attr(max_grouping);
sdw_dpn_attr(simple_ch_prep_sm);
sdw_dpn_attr(ch_prep_timeout);
sdw_dpn_attr(max_ch);
sdw_dpn_attr(min_ch);
sdw_dpn_attr(max_async_buffer);
sdw_dpn_attr(block_pack_mode);
sdw_dpn_attr(port_encoding);
#define sdw_dpn_array_attr(field) \
\
static ssize_t field##_dpn_show(struct sdw_slave *slave, \
int N, \
int dir, \
const char *format_string, \
char *buf) \
{ \
struct sdw_dpn_prop *dpn; \
unsigned long mask; \
ssize_t size = 0; \
int bit; \
int i; \
int j; \
\
if (dir) { \
dpn = slave->prop.src_dpn_prop; \
mask = slave->prop.source_ports; \
} else { \
dpn = slave->prop.sink_dpn_prop; \
mask = slave->prop.sink_ports; \
} \
\
i = 0; \
for_each_set_bit(bit, &mask, 32) { \
if (bit == N) { \
for (j = 0; j < dpn[i].num_##field; j++) \
size += sprintf(buf + size, \
format_string, \
dpn[i].field[j]); \
size += sprintf(buf + size, "\n"); \
return size; \
} \
i++; \
} \
return -EINVAL; \
} \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
struct dpn_attribute *dpn_attr = \
container_of(attr, struct dpn_attribute, dev_attr); \
\
return field##_dpn_show(slave, \
dpn_attr->N, dpn_attr->dir, \
dpn_attr->format_string, \
buf); \
} \
sdw_dpn_attribute_alloc(field)
sdw_dpn_array_attr(words);
sdw_dpn_array_attr(ch_combinations);
sdw_dpn_array_attr(channels);
static int add_all_attributes(struct device *dev, int N, int dir)
{
struct attribute **dpn_attrs;
struct attribute_group *dpn_group;
int i = 0;
int ret;
/* allocate attributes, last one is NULL */
dpn_attrs = devm_kcalloc(dev, SDW_DPN_ATTRIBUTES + 1,
sizeof(struct attribute *),
GFP_KERNEL);
if (!dpn_attrs)
return -ENOMEM;
ret = max_word_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = min_word_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = words_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = type_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_grouping_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = simple_ch_prep_sm_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = ch_prep_timeout_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = imp_def_interrupts_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "0x%x\n");
if (ret < 0)
return ret;
ret = min_ch_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_ch_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = channels_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = ch_combinations_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_async_buffer_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = block_pack_mode_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = port_encoding_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
/* paranioa check for editing mistakes */
if (i != SDW_DPN_ATTRIBUTES) {
dev_err(dev, "mismatch in attributes, allocated %d got %d\n",
SDW_DPN_ATTRIBUTES, i);
return -EINVAL;
}
dpn_group = devm_kzalloc(dev, sizeof(*dpn_group), GFP_KERNEL);
if (!dpn_group)
return -ENOMEM;
dpn_group->attrs = dpn_attrs;
dpn_group->name = devm_kasprintf(dev, GFP_KERNEL, "dp%d_%s",
N, dir ? "src" : "sink");
if (!dpn_group->name)
return -ENOMEM;
ret = devm_device_add_group(dev, dpn_group);
if (ret < 0)
return ret;
return 0;
}
int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave)
{
unsigned long mask;
int ret;
int i;
mask = slave->prop.source_ports;
for_each_set_bit(i, &mask, 32) {
ret = add_all_attributes(&slave->dev, i, 1);
if (ret < 0)
return ret;
}
mask = slave->prop.sink_ports;
for_each_set_bit(i, &mask, 32) {
ret = add_all_attributes(&slave->dev, i, 0);
if (ret < 0)
return ret;
}
return 0;
}