| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Generic Counter sysfs interface | 
 |  * Copyright (C) 2020 William Breathitt Gray | 
 |  */ | 
 | #include <linux/counter.h> | 
 | #include <linux/device.h> | 
 | #include <linux/err.h> | 
 | #include <linux/gfp.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/kfifo.h> | 
 | #include <linux/kstrtox.h> | 
 | #include <linux/list.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/spinlock.h> | 
 | #include <linux/string.h> | 
 | #include <linux/sysfs.h> | 
 | #include <linux/types.h> | 
 |  | 
 | #include "counter-sysfs.h" | 
 |  | 
 | static inline struct counter_device *counter_from_dev(struct device *dev) | 
 | { | 
 | 	return container_of(dev, struct counter_device, dev); | 
 | } | 
 |  | 
 | /** | 
 |  * struct counter_attribute - Counter sysfs attribute | 
 |  * @dev_attr:	device attribute for sysfs | 
 |  * @l:		node to add Counter attribute to attribute group list | 
 |  * @comp:	Counter component callbacks and data | 
 |  * @scope:	Counter scope of the attribute | 
 |  * @parent:	pointer to the parent component | 
 |  */ | 
 | struct counter_attribute { | 
 | 	struct device_attribute dev_attr; | 
 | 	struct list_head l; | 
 |  | 
 | 	struct counter_comp comp; | 
 | 	enum counter_scope scope; | 
 | 	void *parent; | 
 | }; | 
 |  | 
 | #define to_counter_attribute(_dev_attr) \ | 
 | 	container_of(_dev_attr, struct counter_attribute, dev_attr) | 
 |  | 
 | /** | 
 |  * struct counter_attribute_group - container for attribute group | 
 |  * @name:	name of the attribute group | 
 |  * @attr_list:	list to keep track of created attributes | 
 |  * @num_attr:	number of attributes | 
 |  */ | 
 | struct counter_attribute_group { | 
 | 	const char *name; | 
 | 	struct list_head attr_list; | 
 | 	size_t num_attr; | 
 | }; | 
 |  | 
 | static const char *const counter_function_str[] = { | 
 | 	[COUNTER_FUNCTION_INCREASE] = "increase", | 
 | 	[COUNTER_FUNCTION_DECREASE] = "decrease", | 
 | 	[COUNTER_FUNCTION_PULSE_DIRECTION] = "pulse-direction", | 
 | 	[COUNTER_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a", | 
 | 	[COUNTER_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b", | 
 | 	[COUNTER_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a", | 
 | 	[COUNTER_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b", | 
 | 	[COUNTER_FUNCTION_QUADRATURE_X4] = "quadrature x4" | 
 | }; | 
 |  | 
 | static const char *const counter_signal_value_str[] = { | 
 | 	[COUNTER_SIGNAL_LEVEL_LOW] = "low", | 
 | 	[COUNTER_SIGNAL_LEVEL_HIGH] = "high" | 
 | }; | 
 |  | 
 | static const char *const counter_synapse_action_str[] = { | 
 | 	[COUNTER_SYNAPSE_ACTION_NONE] = "none", | 
 | 	[COUNTER_SYNAPSE_ACTION_RISING_EDGE] = "rising edge", | 
 | 	[COUNTER_SYNAPSE_ACTION_FALLING_EDGE] = "falling edge", | 
 | 	[COUNTER_SYNAPSE_ACTION_BOTH_EDGES] = "both edges" | 
 | }; | 
 |  | 
 | static const char *const counter_count_direction_str[] = { | 
 | 	[COUNTER_COUNT_DIRECTION_FORWARD] = "forward", | 
 | 	[COUNTER_COUNT_DIRECTION_BACKWARD] = "backward" | 
 | }; | 
 |  | 
 | static const char *const counter_count_mode_str[] = { | 
 | 	[COUNTER_COUNT_MODE_NORMAL] = "normal", | 
 | 	[COUNTER_COUNT_MODE_RANGE_LIMIT] = "range limit", | 
 | 	[COUNTER_COUNT_MODE_NON_RECYCLE] = "non-recycle", | 
 | 	[COUNTER_COUNT_MODE_MODULO_N] = "modulo-n", | 
 | 	[COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT] = "interrupt on terminal count", | 
 | 	[COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT] = "hardware retriggerable one-shot", | 
 | 	[COUNTER_COUNT_MODE_RATE_GENERATOR] = "rate generator", | 
 | 	[COUNTER_COUNT_MODE_SQUARE_WAVE_MODE] = "square wave mode", | 
 | 	[COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE] = "software triggered strobe", | 
 | 	[COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE] = "hardware triggered strobe", | 
 | }; | 
 |  | 
 | static const char *const counter_signal_polarity_str[] = { | 
 | 	[COUNTER_SIGNAL_POLARITY_POSITIVE] = "positive", | 
 | 	[COUNTER_SIGNAL_POLARITY_NEGATIVE] = "negative" | 
 | }; | 
 |  | 
 | static ssize_t counter_comp_u8_show(struct device *dev, | 
 | 				    struct device_attribute *attr, char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	int err; | 
 | 	u8 data = 0; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u8_read(counter, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u8_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_u8_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	if (a->comp.type == COUNTER_COMP_BOOL) | 
 | 		/* data should already be boolean but ensure just to be safe */ | 
 | 		data = !!data; | 
 |  | 
 | 	return sysfs_emit(buf, "%u\n", (unsigned int)data); | 
 | } | 
 |  | 
 | static ssize_t counter_comp_u8_store(struct device *dev, | 
 | 				     struct device_attribute *attr, | 
 | 				     const char *buf, size_t len) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	int err; | 
 | 	bool bool_data = 0; | 
 | 	u8 data = 0; | 
 |  | 
 | 	if (a->comp.type == COUNTER_COMP_BOOL) { | 
 | 		err = kstrtobool(buf, &bool_data); | 
 | 		data = bool_data; | 
 | 	} else | 
 | 		err = kstrtou8(buf, 0, &data); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u8_write(counter, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u8_write(counter, a->parent, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_u8_write(counter, a->parent, data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_u32_show(struct device *dev, | 
 | 				     struct device_attribute *attr, char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	const struct counter_available *const avail = a->comp.priv; | 
 | 	int err; | 
 | 	u32 data = 0; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u32_read(counter, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u32_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION) | 
 | 			err = a->comp.action_read(counter, a->parent, | 
 | 						  a->comp.priv, &data); | 
 | 		else | 
 | 			err = a->comp.count_u32_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	switch (a->comp.type) { | 
 | 	case COUNTER_COMP_FUNCTION: | 
 | 		return sysfs_emit(buf, "%s\n", counter_function_str[data]); | 
 | 	case COUNTER_COMP_SIGNAL_LEVEL: | 
 | 		return sysfs_emit(buf, "%s\n", counter_signal_value_str[data]); | 
 | 	case COUNTER_COMP_SYNAPSE_ACTION: | 
 | 		return sysfs_emit(buf, "%s\n", counter_synapse_action_str[data]); | 
 | 	case COUNTER_COMP_ENUM: | 
 | 		return sysfs_emit(buf, "%s\n", avail->strs[data]); | 
 | 	case COUNTER_COMP_COUNT_DIRECTION: | 
 | 		return sysfs_emit(buf, "%s\n", counter_count_direction_str[data]); | 
 | 	case COUNTER_COMP_COUNT_MODE: | 
 | 		return sysfs_emit(buf, "%s\n", counter_count_mode_str[data]); | 
 | 	case COUNTER_COMP_SIGNAL_POLARITY: | 
 | 		return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]); | 
 | 	default: | 
 | 		return sysfs_emit(buf, "%u\n", (unsigned int)data); | 
 | 	} | 
 | } | 
 |  | 
 | static int counter_find_enum(u32 *const enum_item, const u32 *const enums, | 
 | 			     const size_t num_enums, const char *const buf, | 
 | 			     const char *const string_array[]) | 
 | { | 
 | 	size_t index; | 
 |  | 
 | 	for (index = 0; index < num_enums; index++) { | 
 | 		*enum_item = enums[index]; | 
 | 		if (sysfs_streq(buf, string_array[*enum_item])) | 
 | 			return 0; | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_u32_store(struct device *dev, | 
 | 				      struct device_attribute *attr, | 
 | 				      const char *buf, size_t len) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	struct counter_count *const count = a->parent; | 
 | 	struct counter_synapse *const synapse = a->comp.priv; | 
 | 	const struct counter_available *const avail = a->comp.priv; | 
 | 	int err; | 
 | 	u32 data = 0; | 
 |  | 
 | 	switch (a->comp.type) { | 
 | 	case COUNTER_COMP_FUNCTION: | 
 | 		err = counter_find_enum(&data, count->functions_list, | 
 | 					count->num_functions, buf, | 
 | 					counter_function_str); | 
 | 		break; | 
 | 	case COUNTER_COMP_SYNAPSE_ACTION: | 
 | 		err = counter_find_enum(&data, synapse->actions_list, | 
 | 					synapse->num_actions, buf, | 
 | 					counter_synapse_action_str); | 
 | 		break; | 
 | 	case COUNTER_COMP_ENUM: | 
 | 		err = __sysfs_match_string(avail->strs, avail->num_items, buf); | 
 | 		data = err; | 
 | 		break; | 
 | 	case COUNTER_COMP_COUNT_MODE: | 
 | 		err = counter_find_enum(&data, avail->enums, avail->num_items, | 
 | 					buf, counter_count_mode_str); | 
 | 		break; | 
 | 	case COUNTER_COMP_SIGNAL_POLARITY: | 
 | 		err = counter_find_enum(&data, avail->enums, avail->num_items, | 
 | 					buf, counter_signal_polarity_str); | 
 | 		break; | 
 | 	default: | 
 | 		err = kstrtou32(buf, 0, &data); | 
 | 		break; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u32_write(counter, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u32_write(counter, a->parent, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION) | 
 | 			err = a->comp.action_write(counter, count, synapse, | 
 | 						   data); | 
 | 		else | 
 | 			err = a->comp.count_u32_write(counter, count, data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_u64_show(struct device *dev, | 
 | 				     struct device_attribute *attr, char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	int err; | 
 | 	u64 data = 0; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u64_read(counter, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u64_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_u64_read(counter, a->parent, &data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return sysfs_emit(buf, "%llu\n", (unsigned long long)data); | 
 | } | 
 |  | 
 | static ssize_t counter_comp_u64_store(struct device *dev, | 
 | 				      struct device_attribute *attr, | 
 | 				      const char *buf, size_t len) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	int err; | 
 | 	u64 data = 0; | 
 |  | 
 | 	err = kstrtou64(buf, 0, &data); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_u64_write(counter, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_u64_write(counter, a->parent, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_u64_write(counter, a->parent, data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_array_u32_show(struct device *dev, | 
 | 					   struct device_attribute *attr, | 
 | 					   char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	const struct counter_array *const element = a->comp.priv; | 
 | 	int err; | 
 | 	u32 data = 0; | 
 |  | 
 | 	if (a->scope != COUNTER_SCOPE_SIGNAL || | 
 | 	    element->type != COUNTER_COMP_SIGNAL_POLARITY) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = a->comp.signal_array_u32_read(counter, a->parent, element->idx, | 
 | 					    &data); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]); | 
 | } | 
 |  | 
 | static ssize_t counter_comp_array_u32_store(struct device *dev, | 
 | 					    struct device_attribute *attr, | 
 | 					    const char *buf, size_t len) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	const struct counter_array *const element = a->comp.priv; | 
 | 	int err; | 
 | 	u32 data = 0; | 
 |  | 
 | 	if (element->type != COUNTER_COMP_SIGNAL_POLARITY || | 
 | 	    a->scope != COUNTER_SCOPE_SIGNAL) | 
 | 		return -EINVAL; | 
 |  | 
 | 	err = counter_find_enum(&data, element->avail->enums, | 
 | 				element->avail->num_items, buf, | 
 | 				counter_signal_polarity_str); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	err = a->comp.signal_array_u32_write(counter, a->parent, element->idx, | 
 | 					     data); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_array_u64_show(struct device *dev, | 
 | 					   struct device_attribute *attr, | 
 | 					   char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	const struct counter_array *const element = a->comp.priv; | 
 | 	int err; | 
 | 	u64 data = 0; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_array_u64_read(counter, element->idx, | 
 | 						    &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_array_u64_read(counter, a->parent, | 
 | 						    element->idx, &data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_array_u64_read(counter, a->parent, | 
 | 						   element->idx, &data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return sysfs_emit(buf, "%llu\n", (unsigned long long)data); | 
 | } | 
 |  | 
 | static ssize_t counter_comp_array_u64_store(struct device *dev, | 
 | 					    struct device_attribute *attr, | 
 | 					    const char *buf, size_t len) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	struct counter_device *const counter = counter_from_dev(dev); | 
 | 	const struct counter_array *const element = a->comp.priv; | 
 | 	int err; | 
 | 	u64 data = 0; | 
 |  | 
 | 	err = kstrtou64(buf, 0, &data); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	switch (a->scope) { | 
 | 	case COUNTER_SCOPE_DEVICE: | 
 | 		err = a->comp.device_array_u64_write(counter, element->idx, | 
 | 						     data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_SIGNAL: | 
 | 		err = a->comp.signal_array_u64_write(counter, a->parent, | 
 | 						     element->idx, data); | 
 | 		break; | 
 | 	case COUNTER_SCOPE_COUNT: | 
 | 		err = a->comp.count_array_u64_write(counter, a->parent, | 
 | 						    element->idx, data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t enums_available_show(const u32 *const enums, | 
 | 				    const size_t num_enums, | 
 | 				    const char *const strs[], char *buf) | 
 | { | 
 | 	size_t len = 0; | 
 | 	size_t index; | 
 |  | 
 | 	for (index = 0; index < num_enums; index++) | 
 | 		len += sysfs_emit_at(buf, len, "%s\n", strs[enums[index]]); | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t strs_available_show(const struct counter_available *const avail, | 
 | 				   char *buf) | 
 | { | 
 | 	size_t len = 0; | 
 | 	size_t index; | 
 |  | 
 | 	for (index = 0; index < avail->num_items; index++) | 
 | 		len += sysfs_emit_at(buf, len, "%s\n", avail->strs[index]); | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_available_show(struct device *dev, | 
 | 					   struct device_attribute *attr, | 
 | 					   char *buf) | 
 | { | 
 | 	const struct counter_attribute *const a = to_counter_attribute(attr); | 
 | 	const struct counter_count *const count = a->parent; | 
 | 	const struct counter_synapse *const synapse = a->comp.priv; | 
 | 	const struct counter_available *const avail = a->comp.priv; | 
 |  | 
 | 	switch (a->comp.type) { | 
 | 	case COUNTER_COMP_FUNCTION: | 
 | 		return enums_available_show(count->functions_list, | 
 | 					    count->num_functions, | 
 | 					    counter_function_str, buf); | 
 | 	case COUNTER_COMP_SYNAPSE_ACTION: | 
 | 		return enums_available_show(synapse->actions_list, | 
 | 					    synapse->num_actions, | 
 | 					    counter_synapse_action_str, buf); | 
 | 	case COUNTER_COMP_ENUM: | 
 | 		return strs_available_show(avail, buf); | 
 | 	case COUNTER_COMP_COUNT_MODE: | 
 | 		return enums_available_show(avail->enums, avail->num_items, | 
 | 					    counter_count_mode_str, buf); | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 | } | 
 |  | 
 | static int counter_avail_attr_create(struct device *const dev, | 
 | 	struct counter_attribute_group *const group, | 
 | 	const struct counter_comp *const comp, void *const parent) | 
 | { | 
 | 	struct counter_attribute *counter_attr; | 
 | 	struct device_attribute *dev_attr; | 
 |  | 
 | 	counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL); | 
 | 	if (!counter_attr) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Configure Counter attribute */ | 
 | 	counter_attr->comp.type = comp->type; | 
 | 	counter_attr->comp.priv = comp->priv; | 
 | 	counter_attr->parent = parent; | 
 |  | 
 | 	/* Initialize sysfs attribute */ | 
 | 	dev_attr = &counter_attr->dev_attr; | 
 | 	sysfs_attr_init(&dev_attr->attr); | 
 |  | 
 | 	/* Configure device attribute */ | 
 | 	dev_attr->attr.name = devm_kasprintf(dev, GFP_KERNEL, "%s_available", | 
 | 					     comp->name); | 
 | 	if (!dev_attr->attr.name) | 
 | 		return -ENOMEM; | 
 | 	dev_attr->attr.mode = 0444; | 
 | 	dev_attr->show = counter_comp_available_show; | 
 |  | 
 | 	/* Store list node */ | 
 | 	list_add(&counter_attr->l, &group->attr_list); | 
 | 	group->num_attr++; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_attr_create(struct device *const dev, | 
 | 			       struct counter_attribute_group *const group, | 
 | 			       const struct counter_comp *const comp, | 
 | 			       const enum counter_scope scope, | 
 | 			       void *const parent) | 
 | { | 
 | 	const struct counter_array *const array = comp->priv; | 
 | 	struct counter_attribute *counter_attr; | 
 | 	struct device_attribute *dev_attr; | 
 |  | 
 | 	counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL); | 
 | 	if (!counter_attr) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Configure Counter attribute */ | 
 | 	counter_attr->comp = *comp; | 
 | 	counter_attr->scope = scope; | 
 | 	counter_attr->parent = parent; | 
 |  | 
 | 	/* Configure device attribute */ | 
 | 	dev_attr = &counter_attr->dev_attr; | 
 | 	sysfs_attr_init(&dev_attr->attr); | 
 | 	dev_attr->attr.name = comp->name; | 
 | 	switch (comp->type) { | 
 | 	case COUNTER_COMP_U8: | 
 | 	case COUNTER_COMP_BOOL: | 
 | 		if (comp->device_u8_read) { | 
 | 			dev_attr->attr.mode |= 0444; | 
 | 			dev_attr->show = counter_comp_u8_show; | 
 | 		} | 
 | 		if (comp->device_u8_write) { | 
 | 			dev_attr->attr.mode |= 0200; | 
 | 			dev_attr->store = counter_comp_u8_store; | 
 | 		} | 
 | 		break; | 
 | 	case COUNTER_COMP_SIGNAL_LEVEL: | 
 | 	case COUNTER_COMP_FUNCTION: | 
 | 	case COUNTER_COMP_SYNAPSE_ACTION: | 
 | 	case COUNTER_COMP_ENUM: | 
 | 	case COUNTER_COMP_COUNT_DIRECTION: | 
 | 	case COUNTER_COMP_COUNT_MODE: | 
 | 	case COUNTER_COMP_SIGNAL_POLARITY: | 
 | 		if (comp->device_u32_read) { | 
 | 			dev_attr->attr.mode |= 0444; | 
 | 			dev_attr->show = counter_comp_u32_show; | 
 | 		} | 
 | 		if (comp->device_u32_write) { | 
 | 			dev_attr->attr.mode |= 0200; | 
 | 			dev_attr->store = counter_comp_u32_store; | 
 | 		} | 
 | 		break; | 
 | 	case COUNTER_COMP_U64: | 
 | 		if (comp->device_u64_read) { | 
 | 			dev_attr->attr.mode |= 0444; | 
 | 			dev_attr->show = counter_comp_u64_show; | 
 | 		} | 
 | 		if (comp->device_u64_write) { | 
 | 			dev_attr->attr.mode |= 0200; | 
 | 			dev_attr->store = counter_comp_u64_store; | 
 | 		} | 
 | 		break; | 
 | 	case COUNTER_COMP_ARRAY: | 
 | 		switch (array->type) { | 
 | 		case COUNTER_COMP_SIGNAL_POLARITY: | 
 | 			if (comp->signal_array_u32_read) { | 
 | 				dev_attr->attr.mode |= 0444; | 
 | 				dev_attr->show = counter_comp_array_u32_show; | 
 | 			} | 
 | 			if (comp->signal_array_u32_write) { | 
 | 				dev_attr->attr.mode |= 0200; | 
 | 				dev_attr->store = counter_comp_array_u32_store; | 
 | 			} | 
 | 			break; | 
 | 		case COUNTER_COMP_U64: | 
 | 			if (comp->device_array_u64_read) { | 
 | 				dev_attr->attr.mode |= 0444; | 
 | 				dev_attr->show = counter_comp_array_u64_show; | 
 | 			} | 
 | 			if (comp->device_array_u64_write) { | 
 | 				dev_attr->attr.mode |= 0200; | 
 | 				dev_attr->store = counter_comp_array_u64_store; | 
 | 			} | 
 | 			break; | 
 | 		default: | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Store list node */ | 
 | 	list_add(&counter_attr->l, &group->attr_list); | 
 | 	group->num_attr++; | 
 |  | 
 | 	/* Create "*_available" attribute if needed */ | 
 | 	switch (comp->type) { | 
 | 	case COUNTER_COMP_FUNCTION: | 
 | 	case COUNTER_COMP_SYNAPSE_ACTION: | 
 | 	case COUNTER_COMP_ENUM: | 
 | 	case COUNTER_COMP_COUNT_MODE: | 
 | 		return counter_avail_attr_create(dev, group, comp, parent); | 
 | 	default: | 
 | 		return 0; | 
 | 	} | 
 | } | 
 |  | 
 | static ssize_t counter_comp_name_show(struct device *dev, | 
 | 				      struct device_attribute *attr, char *buf) | 
 | { | 
 | 	return sysfs_emit(buf, "%s\n", to_counter_attribute(attr)->comp.name); | 
 | } | 
 |  | 
 | static int counter_name_attr_create(struct device *const dev, | 
 | 				    struct counter_attribute_group *const group, | 
 | 				    const char *const name) | 
 | { | 
 | 	struct counter_attribute *counter_attr; | 
 |  | 
 | 	counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL); | 
 | 	if (!counter_attr) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Configure Counter attribute */ | 
 | 	counter_attr->comp.name = name; | 
 |  | 
 | 	/* Configure device attribute */ | 
 | 	sysfs_attr_init(&counter_attr->dev_attr.attr); | 
 | 	counter_attr->dev_attr.attr.name = "name"; | 
 | 	counter_attr->dev_attr.attr.mode = 0444; | 
 | 	counter_attr->dev_attr.show = counter_comp_name_show; | 
 |  | 
 | 	/* Store list node */ | 
 | 	list_add(&counter_attr->l, &group->attr_list); | 
 | 	group->num_attr++; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static ssize_t counter_comp_id_show(struct device *dev, | 
 | 				    struct device_attribute *attr, char *buf) | 
 | { | 
 | 	const size_t id = (size_t)to_counter_attribute(attr)->comp.priv; | 
 |  | 
 | 	return sysfs_emit(buf, "%zu\n", id); | 
 | } | 
 |  | 
 | static int counter_comp_id_attr_create(struct device *const dev, | 
 | 				       struct counter_attribute_group *const group, | 
 | 				       const char *name, const size_t id) | 
 | { | 
 | 	struct counter_attribute *counter_attr; | 
 |  | 
 | 	/* Allocate Counter attribute */ | 
 | 	counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL); | 
 | 	if (!counter_attr) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Generate component ID name */ | 
 | 	name = devm_kasprintf(dev, GFP_KERNEL, "%s_component_id", name); | 
 | 	if (!name) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Configure Counter attribute */ | 
 | 	counter_attr->comp.priv = (void *)id; | 
 |  | 
 | 	/* Configure device attribute */ | 
 | 	sysfs_attr_init(&counter_attr->dev_attr.attr); | 
 | 	counter_attr->dev_attr.attr.name = name; | 
 | 	counter_attr->dev_attr.attr.mode = 0444; | 
 | 	counter_attr->dev_attr.show = counter_comp_id_show; | 
 |  | 
 | 	/* Store list node */ | 
 | 	list_add(&counter_attr->l, &group->attr_list); | 
 | 	group->num_attr++; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_ext_attrs_create(struct device *const dev, | 
 | 				    struct counter_attribute_group *const group, | 
 | 				    const struct counter_comp *const ext, | 
 | 				    const enum counter_scope scope, | 
 | 				    void *const parent, const size_t id) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	/* Create main extension attribute */ | 
 | 	err = counter_attr_create(dev, group, ext, scope, parent); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create extension id attribute */ | 
 | 	return counter_comp_id_attr_create(dev, group, ext->name, id); | 
 | } | 
 |  | 
 | static int counter_array_attrs_create(struct device *const dev, | 
 | 				      struct counter_attribute_group *const group, | 
 | 				      const struct counter_comp *const comp, | 
 | 				      const enum counter_scope scope, | 
 | 				      void *const parent, const size_t id) | 
 | { | 
 | 	const struct counter_array *const array = comp->priv; | 
 | 	struct counter_comp ext = *comp; | 
 | 	struct counter_array *element; | 
 | 	size_t idx; | 
 | 	int err; | 
 |  | 
 | 	/* Create an attribute for each array element */ | 
 | 	for (idx = 0; idx < array->length; idx++) { | 
 | 		/* Generate array element attribute name */ | 
 | 		ext.name = devm_kasprintf(dev, GFP_KERNEL, "%s%zu", comp->name, | 
 | 					  idx); | 
 | 		if (!ext.name) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		/* Allocate and configure array element */ | 
 | 		element = devm_kzalloc(dev, sizeof(*element), GFP_KERNEL); | 
 | 		if (!element) | 
 | 			return -ENOMEM; | 
 | 		element->type = array->type; | 
 | 		element->avail = array->avail; | 
 | 		element->idx = idx; | 
 | 		ext.priv = element; | 
 |  | 
 | 		/* Create all attributes associated with the array element */ | 
 | 		err = counter_ext_attrs_create(dev, group, &ext, scope, parent, | 
 | 					       id + idx); | 
 | 		if (err < 0) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_sysfs_exts_add(struct device *const dev, | 
 | 				  struct counter_attribute_group *const group, | 
 | 				  const struct counter_comp *const exts, | 
 | 				  const size_t num_ext, | 
 | 				  const enum counter_scope scope, | 
 | 				  void *const parent) | 
 | { | 
 | 	size_t i; | 
 | 	const struct counter_comp *ext; | 
 | 	int err; | 
 | 	size_t id = 0; | 
 | 	const struct counter_array *array; | 
 |  | 
 | 	/* Create attributes for each extension */ | 
 | 	for (i = 0; i < num_ext; i++) { | 
 | 		ext = &exts[i]; | 
 | 		if (ext->type == COUNTER_COMP_ARRAY) { | 
 | 			err = counter_array_attrs_create(dev, group, ext, scope, | 
 | 							 parent, id); | 
 | 			array = ext->priv; | 
 | 			id += array->length; | 
 | 		} else { | 
 | 			err = counter_ext_attrs_create(dev, group, ext, scope, | 
 | 						       parent, id); | 
 | 			id++; | 
 | 		} | 
 | 		if (err < 0) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct counter_comp counter_signal_comp = { | 
 | 	.type = COUNTER_COMP_SIGNAL_LEVEL, | 
 | 	.name = "signal", | 
 | }; | 
 |  | 
 | static int counter_signal_attrs_create(struct counter_device *const counter, | 
 | 	struct counter_attribute_group *const cattr_group, | 
 | 	struct counter_signal *const signal) | 
 | { | 
 | 	const enum counter_scope scope = COUNTER_SCOPE_SIGNAL; | 
 | 	struct device *const dev = &counter->dev; | 
 | 	int err; | 
 | 	struct counter_comp comp; | 
 |  | 
 | 	/* Create main Signal attribute */ | 
 | 	comp = counter_signal_comp; | 
 | 	comp.signal_u32_read = counter->ops->signal_read; | 
 | 	err = counter_attr_create(dev, cattr_group, &comp, scope, signal); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create Signal name attribute */ | 
 | 	err = counter_name_attr_create(dev, cattr_group, signal->name); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Add Signal extensions */ | 
 | 	return counter_sysfs_exts_add(dev, cattr_group, signal->ext, | 
 | 				      signal->num_ext, scope, signal); | 
 | } | 
 |  | 
 | static int counter_sysfs_signals_add(struct counter_device *const counter, | 
 | 	struct counter_attribute_group *const groups) | 
 | { | 
 | 	size_t i; | 
 | 	int err; | 
 |  | 
 | 	/* Add each Signal */ | 
 | 	for (i = 0; i < counter->num_signals; i++) { | 
 | 		/* Generate Signal attribute directory name */ | 
 | 		groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL, | 
 | 						"signal%zu", i); | 
 | 		if (!groups[i].name) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		/* Create all attributes associated with Signal */ | 
 | 		err = counter_signal_attrs_create(counter, groups + i, | 
 | 						  counter->signals + i); | 
 | 		if (err < 0) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_sysfs_synapses_add(struct counter_device *const counter, | 
 | 	struct counter_attribute_group *const group, | 
 | 	struct counter_count *const count) | 
 | { | 
 | 	size_t i; | 
 |  | 
 | 	/* Add each Synapse */ | 
 | 	for (i = 0; i < count->num_synapses; i++) { | 
 | 		struct device *const dev = &counter->dev; | 
 | 		struct counter_synapse *synapse; | 
 | 		size_t id; | 
 | 		struct counter_comp comp; | 
 | 		int err; | 
 |  | 
 | 		synapse = count->synapses + i; | 
 |  | 
 | 		/* Generate Synapse action name */ | 
 | 		id = synapse->signal - counter->signals; | 
 | 		comp.name = devm_kasprintf(dev, GFP_KERNEL, "signal%zu_action", | 
 | 					   id); | 
 | 		if (!comp.name) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		/* Create action attribute */ | 
 | 		comp.type = COUNTER_COMP_SYNAPSE_ACTION; | 
 | 		comp.action_read = counter->ops->action_read; | 
 | 		comp.action_write = counter->ops->action_write; | 
 | 		comp.priv = synapse; | 
 | 		err = counter_attr_create(dev, group, &comp, | 
 | 					  COUNTER_SCOPE_COUNT, count); | 
 | 		if (err < 0) | 
 | 			return err; | 
 |  | 
 | 		/* Create Synapse component ID attribute */ | 
 | 		err = counter_comp_id_attr_create(dev, group, comp.name, i); | 
 | 		if (err < 0) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct counter_comp counter_count_comp = | 
 | 	COUNTER_COMP_COUNT_U64("count", NULL, NULL); | 
 |  | 
 | static struct counter_comp counter_function_comp = { | 
 | 	.type = COUNTER_COMP_FUNCTION, | 
 | 	.name = "function", | 
 | }; | 
 |  | 
 | static int counter_count_attrs_create(struct counter_device *const counter, | 
 | 	struct counter_attribute_group *const cattr_group, | 
 | 	struct counter_count *const count) | 
 | { | 
 | 	const enum counter_scope scope = COUNTER_SCOPE_COUNT; | 
 | 	struct device *const dev = &counter->dev; | 
 | 	int err; | 
 | 	struct counter_comp comp; | 
 |  | 
 | 	/* Create main Count attribute */ | 
 | 	comp = counter_count_comp; | 
 | 	comp.count_u64_read = counter->ops->count_read; | 
 | 	comp.count_u64_write = counter->ops->count_write; | 
 | 	err = counter_attr_create(dev, cattr_group, &comp, scope, count); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create Count name attribute */ | 
 | 	err = counter_name_attr_create(dev, cattr_group, count->name); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create Count function attribute */ | 
 | 	comp = counter_function_comp; | 
 | 	comp.count_u32_read = counter->ops->function_read; | 
 | 	comp.count_u32_write = counter->ops->function_write; | 
 | 	err = counter_attr_create(dev, cattr_group, &comp, scope, count); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Add Count extensions */ | 
 | 	return counter_sysfs_exts_add(dev, cattr_group, count->ext, | 
 | 				      count->num_ext, scope, count); | 
 | } | 
 |  | 
 | static int counter_sysfs_counts_add(struct counter_device *const counter, | 
 | 	struct counter_attribute_group *const groups) | 
 | { | 
 | 	size_t i; | 
 | 	struct counter_count *count; | 
 | 	int err; | 
 |  | 
 | 	/* Add each Count */ | 
 | 	for (i = 0; i < counter->num_counts; i++) { | 
 | 		count = counter->counts + i; | 
 |  | 
 | 		/* Generate Count attribute directory name */ | 
 | 		groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL, | 
 | 						"count%zu", i); | 
 | 		if (!groups[i].name) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		/* Add sysfs attributes of the Synapses */ | 
 | 		err = counter_sysfs_synapses_add(counter, groups + i, count); | 
 | 		if (err < 0) | 
 | 			return err; | 
 |  | 
 | 		/* Create all attributes associated with Count */ | 
 | 		err = counter_count_attrs_create(counter, groups + i, count); | 
 | 		if (err < 0) | 
 | 			return err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_num_signals_read(struct counter_device *counter, u8 *val) | 
 | { | 
 | 	*val = counter->num_signals; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_num_counts_read(struct counter_device *counter, u8 *val) | 
 | { | 
 | 	*val = counter->num_counts; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_events_queue_size_read(struct counter_device *counter, | 
 | 					  u64 *val) | 
 | { | 
 | 	*val = kfifo_size(&counter->events); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int counter_events_queue_size_write(struct counter_device *counter, | 
 | 					   u64 val) | 
 | { | 
 | 	DECLARE_KFIFO_PTR(events, struct counter_event); | 
 | 	int err; | 
 | 	unsigned long flags; | 
 |  | 
 | 	/* Allocate new events queue */ | 
 | 	err = kfifo_alloc(&events, val, GFP_KERNEL); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	/* Swap in new events queue */ | 
 | 	mutex_lock(&counter->events_out_lock); | 
 | 	spin_lock_irqsave(&counter->events_in_lock, flags); | 
 | 	kfifo_free(&counter->events); | 
 | 	counter->events.kfifo = events.kfifo; | 
 | 	spin_unlock_irqrestore(&counter->events_in_lock, flags); | 
 | 	mutex_unlock(&counter->events_out_lock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct counter_comp counter_num_signals_comp = | 
 | 	COUNTER_COMP_DEVICE_U8("num_signals", counter_num_signals_read, NULL); | 
 |  | 
 | static struct counter_comp counter_num_counts_comp = | 
 | 	COUNTER_COMP_DEVICE_U8("num_counts", counter_num_counts_read, NULL); | 
 |  | 
 | static struct counter_comp counter_events_queue_size_comp = | 
 | 	COUNTER_COMP_DEVICE_U64("events_queue_size", | 
 | 				counter_events_queue_size_read, | 
 | 				counter_events_queue_size_write); | 
 |  | 
 | static int counter_sysfs_attr_add(struct counter_device *const counter, | 
 | 				  struct counter_attribute_group *cattr_group) | 
 | { | 
 | 	const enum counter_scope scope = COUNTER_SCOPE_DEVICE; | 
 | 	struct device *const dev = &counter->dev; | 
 | 	int err; | 
 |  | 
 | 	/* Add Signals sysfs attributes */ | 
 | 	err = counter_sysfs_signals_add(counter, cattr_group); | 
 | 	if (err < 0) | 
 | 		return err; | 
 | 	cattr_group += counter->num_signals; | 
 |  | 
 | 	/* Add Counts sysfs attributes */ | 
 | 	err = counter_sysfs_counts_add(counter, cattr_group); | 
 | 	if (err < 0) | 
 | 		return err; | 
 | 	cattr_group += counter->num_counts; | 
 |  | 
 | 	/* Create name attribute */ | 
 | 	err = counter_name_attr_create(dev, cattr_group, counter->name); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create num_signals attribute */ | 
 | 	err = counter_attr_create(dev, cattr_group, &counter_num_signals_comp, | 
 | 				  scope, NULL); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create num_counts attribute */ | 
 | 	err = counter_attr_create(dev, cattr_group, &counter_num_counts_comp, | 
 | 				  scope, NULL); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Create events_queue_size attribute */ | 
 | 	err = counter_attr_create(dev, cattr_group, | 
 | 				  &counter_events_queue_size_comp, scope, NULL); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Add device extensions */ | 
 | 	return counter_sysfs_exts_add(dev, cattr_group, counter->ext, | 
 | 				      counter->num_ext, scope, NULL); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * counter_sysfs_add - Adds Counter sysfs attributes to the device structure | 
 |  * @counter:	Pointer to the Counter device structure | 
 |  * | 
 |  * Counter sysfs attributes are created and added to the respective device | 
 |  * structure for later registration to the system. Resource-managed memory | 
 |  * allocation is performed by this function, and this memory should be freed | 
 |  * when no longer needed (automatically by a device_unregister call, or | 
 |  * manually by a devres_release_all call). | 
 |  */ | 
 | int counter_sysfs_add(struct counter_device *const counter) | 
 | { | 
 | 	struct device *const dev = &counter->dev; | 
 | 	const size_t num_groups = counter->num_signals + counter->num_counts + 1; | 
 | 	struct counter_attribute_group *cattr_groups; | 
 | 	size_t i, j; | 
 | 	int err; | 
 | 	struct attribute_group *groups; | 
 | 	struct counter_attribute *p; | 
 |  | 
 | 	/* Allocate space for attribute groups (signals, counts, and ext) */ | 
 | 	cattr_groups = devm_kcalloc(dev, num_groups, sizeof(*cattr_groups), | 
 | 				    GFP_KERNEL); | 
 | 	if (!cattr_groups) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Initialize attribute lists */ | 
 | 	for (i = 0; i < num_groups; i++) | 
 | 		INIT_LIST_HEAD(&cattr_groups[i].attr_list); | 
 |  | 
 | 	/* Add Counter device sysfs attributes */ | 
 | 	err = counter_sysfs_attr_add(counter, cattr_groups); | 
 | 	if (err < 0) | 
 | 		return err; | 
 |  | 
 | 	/* Allocate attribute group pointers for association with device */ | 
 | 	dev->groups = devm_kcalloc(dev, num_groups + 1, sizeof(*dev->groups), | 
 | 				   GFP_KERNEL); | 
 | 	if (!dev->groups) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Allocate space for attribute groups */ | 
 | 	groups = devm_kcalloc(dev, num_groups, sizeof(*groups), GFP_KERNEL); | 
 | 	if (!groups) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Prepare each group of attributes for association */ | 
 | 	for (i = 0; i < num_groups; i++) { | 
 | 		groups[i].name = cattr_groups[i].name; | 
 |  | 
 | 		/* Allocate space for attribute pointers */ | 
 | 		groups[i].attrs = devm_kcalloc(dev, | 
 | 					       cattr_groups[i].num_attr + 1, | 
 | 					       sizeof(*groups[i].attrs), | 
 | 					       GFP_KERNEL); | 
 | 		if (!groups[i].attrs) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		/* Add attribute pointers to attribute group */ | 
 | 		j = 0; | 
 | 		list_for_each_entry(p, &cattr_groups[i].attr_list, l) | 
 | 			groups[i].attrs[j++] = &p->dev_attr.attr; | 
 |  | 
 | 		/* Associate attribute group */ | 
 | 		dev->groups[i] = &groups[i]; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } |