// SPDX-License-Identifier: GPL-2.0
/*
 * AVIRT - ALSA Virtual Soundcard
 *
 * Copyright (c) 2010-2019 Fiberdyne Systems Pty Ltd
 *
 * configfs.c - AVIRT sysfs support
 */

#include <linux/slab.h>

#include "core.h"

#define D_LOGNAME "sysfs"

#define D_INFOK(fmt, args...) DINFO(D_LOGNAME, fmt, ##args)
#define D_PRINTK(fmt, args...) DDEBUG(D_LOGNAME, fmt, ##args)
#define D_ERRORK(fmt, args...) DERROR(D_LOGNAME, fmt, ##args)

static struct kset *snd_avirt_audiopath_kset;
static struct kobject *kobj;

#define to_audiopath_obj(d)                                                    \
	container_of(d, struct snd_avirt_audiopath_obj, kobj)
#define to_audiopath_attr(a)                                                   \
	container_of(a, struct snd_avirt_audiopath_attribute, attr)

/**
 * struct snd_avirt_audiopath_attribute - access the attributes of Audio Path
 * @attr: attributes of an Audio Path
 * @show: pointer to the show function
 * @store: pointer to the store function
 */
struct snd_avirt_audiopath_attribute {
	struct attribute attr;
	ssize_t (*show)(struct snd_avirt_audiopath_obj *d,
			struct snd_avirt_audiopath_attribute *attr, char *buf);
	ssize_t (*store)(struct snd_avirt_audiopath_obj *d,
			 struct snd_avirt_audiopath_attribute *attr,
			 const char *buf, size_t count);
};

/**
 * audiopath_attr_show - show function of an Audio Path
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 */
static ssize_t audiopath_attr_show(struct kobject *kobj, struct attribute *attr,
				   char *buf)
{
	struct snd_avirt_audiopath_attribute *audiopath_attr;
	struct snd_avirt_audiopath_obj *audiopath_obj;

	audiopath_attr = to_audiopath_attr(attr);
	audiopath_obj = to_audiopath_obj(kobj);

	if (!audiopath_attr->show)
		return -EIO;

	return audiopath_attr->show(audiopath_obj, audiopath_attr, buf);
}

/**
 * audiopath_attr_store - attribute store function of Audio Path object
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 * @len: length of buffer
 */
static ssize_t audiopath_attr_store(struct kobject *kobj,
				    struct attribute *attr, const char *buf,
				    size_t len)
{
	struct snd_avirt_audiopath_attribute *audiopath_attr;
	struct snd_avirt_audiopath_obj *audiopath_obj;

	audiopath_attr = to_audiopath_attr(attr);
	audiopath_obj = to_audiopath_obj(kobj);

	if (!audiopath_attr->store)
		return -EIO;
	return audiopath_attr->store(audiopath_obj, audiopath_attr, buf, len);
}

static const struct sysfs_ops snd_avirt_audiopath_sysfs_ops = {
	.show = audiopath_attr_show,
	.store = audiopath_attr_store,
};

/**
 * snd_avirt_audiopath_release - Audio Path release function
 * @kobj: pointer to Audio Paths's kobject
 */
static void snd_avirt_audiopath_release(struct kobject *kobj)
{
	struct snd_avirt_audiopath_obj *audiopath_obj = to_audiopath_obj(kobj);

	kfree(audiopath_obj);
}

static ssize_t name_show(struct snd_avirt_audiopath_obj *audiopath_obj,
			 struct snd_avirt_audiopath_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n", audiopath_obj->path->name);
}

static ssize_t version_show(struct snd_avirt_audiopath_obj *audiopath_obj,
			    struct snd_avirt_audiopath_attribute *attr,
			    char *buf)
{
	struct snd_avirt_audiopath *audiopath = audiopath_obj->path;

	return sprintf(buf, "%d.%d.%d\n", audiopath->version[0],
		       audiopath->version[1], audiopath->version[2]);
}

static ssize_t route_from_ap_show(struct snd_avirt_audiopath_obj *audiopath_obj,
				  struct snd_avirt_audiopath_attribute *attr,
				  char *buf)
{
	struct snd_avirt_audiopath *audiopath = audiopath_obj->path;

	if (audiopath->route_from_ap)
		return sprintf(buf, "%s\n", audiopath->route_from_ap->uid);

	return 0;
}

static ssize_t route_to_ap_show(struct snd_avirt_audiopath_obj *audiopath_obj,
				struct snd_avirt_audiopath_attribute *attr,
				char *buf)
{
	struct snd_avirt_audiopath *audiopath = audiopath_obj->path;

	if (audiopath->route_to_ap)
		return sprintf(buf, "%s\n", audiopath->route_to_ap->uid);

	return 0;
}

static struct snd_avirt_audiopath_attribute snd_avirt_audiopath_attrs[] = {
	__ATTR_RO(name),
	__ATTR_RO(version),
	__ATTR_RO(route_from_ap),
	__ATTR_RO(route_to_ap),
};

static struct attribute *snd_avirt_audiopath_def_attrs[] = {
	&snd_avirt_audiopath_attrs[0].attr,
	&snd_avirt_audiopath_attrs[1].attr,
	&snd_avirt_audiopath_attrs[2].attr,
	&snd_avirt_audiopath_attrs[3].attr,
	NULL,
};

static struct kobj_type snd_avirt_audiopath_ktype = {
	.sysfs_ops = &snd_avirt_audiopath_sysfs_ops,
	.release = snd_avirt_audiopath_release,
	.default_attrs = snd_avirt_audiopath_def_attrs,
};

struct snd_avirt_audiopath_obj *snd_avirt_audiopath_create_obj(const char *uid)
{
	struct snd_avirt_audiopath_obj *snd_avirt_audiopath;
	int retval;

	snd_avirt_audiopath = kzalloc(sizeof(*snd_avirt_audiopath), GFP_KERNEL);
	if (!snd_avirt_audiopath)
		return NULL;
	snd_avirt_audiopath->kobj.kset = snd_avirt_audiopath_kset;
	retval = kobject_init_and_add(&snd_avirt_audiopath->kobj,
				      &snd_avirt_audiopath_ktype, kobj, "%s",
				      uid);
	if (retval) {
		kobject_put(&snd_avirt_audiopath->kobj);
		return NULL;
	}
	kobject_uevent(&snd_avirt_audiopath->kobj, KOBJ_ADD);
	return snd_avirt_audiopath;
}

void snd_avirt_audiopath_destroy_obj(struct snd_avirt_audiopath_obj *p)
{
	kobject_put(&p->kobj);
}

int __init snd_avirt_sysfs_init(struct snd_avirt_core *core)
{
	snd_avirt_audiopath_kset =
		kset_create_and_add("audiopaths", NULL, &core->dev->kobj);
	if (!snd_avirt_audiopath_kset)
		return -ENOMEM;

	return 0;
}

void __exit snd_avirt_sysfs_exit(struct snd_avirt_core *core)
{
	kset_unregister(snd_avirt_audiopath_kset);
}