| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC | 
 |  * | 
 |  * Authors: | 
 |  *   Serge Semin <Sergey.Semin@baikalelectronics.ru> | 
 |  * | 
 |  * Baikal-T1 CM2 L2-cache Control Block driver. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/bitfield.h> | 
 | #include <linux/types.h> | 
 | #include <linux/device.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/mfd/syscon.h> | 
 | #include <linux/sysfs.h> | 
 | #include <linux/of.h> | 
 |  | 
 | #define L2_CTL_REG			0x028 | 
 | #define L2_CTL_DATA_STALL_FLD		0 | 
 | #define L2_CTL_DATA_STALL_MASK		GENMASK(1, L2_CTL_DATA_STALL_FLD) | 
 | #define L2_CTL_TAG_STALL_FLD		2 | 
 | #define L2_CTL_TAG_STALL_MASK		GENMASK(3, L2_CTL_TAG_STALL_FLD) | 
 | #define L2_CTL_WS_STALL_FLD		4 | 
 | #define L2_CTL_WS_STALL_MASK		GENMASK(5, L2_CTL_WS_STALL_FLD) | 
 | #define L2_CTL_SET_CLKRATIO		BIT(13) | 
 | #define L2_CTL_CLKRATIO_LOCK		BIT(31) | 
 |  | 
 | #define L2_CTL_STALL_MIN		0 | 
 | #define L2_CTL_STALL_MAX		3 | 
 | #define L2_CTL_STALL_SET_DELAY_US	1 | 
 | #define L2_CTL_STALL_SET_TOUT_US	1000 | 
 |  | 
 | /* | 
 |  * struct l2_ctl - Baikal-T1 L2 Control block private data. | 
 |  * @dev: Pointer to the device structure. | 
 |  * @sys_regs: Baikal-T1 System Controller registers map. | 
 |  */ | 
 | struct l2_ctl { | 
 | 	struct device *dev; | 
 |  | 
 | 	struct regmap *sys_regs; | 
 | }; | 
 |  | 
 | /* | 
 |  * enum l2_ctl_stall - Baikal-T1 L2-cache-RAM stall identifier. | 
 |  * @L2_WSSTALL: Way-select latency. | 
 |  * @L2_TAGSTALL: Tag latency. | 
 |  * @L2_DATASTALL: Data latency. | 
 |  */ | 
 | enum l2_ctl_stall { | 
 | 	L2_WS_STALL, | 
 | 	L2_TAG_STALL, | 
 | 	L2_DATA_STALL | 
 | }; | 
 |  | 
 | /* | 
 |  * struct l2_ctl_device_attribute - Baikal-T1 L2-cache device attribute. | 
 |  * @dev_attr: Actual sysfs device attribute. | 
 |  * @id: L2-cache stall field identifier. | 
 |  */ | 
 | struct l2_ctl_device_attribute { | 
 | 	struct device_attribute dev_attr; | 
 | 	enum l2_ctl_stall id; | 
 | }; | 
 |  | 
 | #define to_l2_ctl_dev_attr(_dev_attr) \ | 
 | 	container_of(_dev_attr, struct l2_ctl_device_attribute, dev_attr) | 
 |  | 
 | #define L2_CTL_ATTR_RW(_name, _prefix, _id) \ | 
 | 	struct l2_ctl_device_attribute l2_ctl_attr_##_name = \ | 
 | 		{ __ATTR(_name, 0644, _prefix##_show, _prefix##_store),	_id } | 
 |  | 
 | static int l2_ctl_get_latency(struct l2_ctl *l2, enum l2_ctl_stall id, u32 *val) | 
 | { | 
 | 	u32 data = 0; | 
 | 	int ret; | 
 |  | 
 | 	ret = regmap_read(l2->sys_regs, L2_CTL_REG, &data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	switch (id) { | 
 | 	case L2_WS_STALL: | 
 | 		*val = FIELD_GET(L2_CTL_WS_STALL_MASK, data); | 
 | 		break; | 
 | 	case L2_TAG_STALL: | 
 | 		*val = FIELD_GET(L2_CTL_TAG_STALL_MASK, data); | 
 | 		break; | 
 | 	case L2_DATA_STALL: | 
 | 		*val = FIELD_GET(L2_CTL_DATA_STALL_MASK, data); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int l2_ctl_set_latency(struct l2_ctl *l2, enum l2_ctl_stall id, u32 val) | 
 | { | 
 | 	u32 mask = 0, data = 0; | 
 | 	int ret; | 
 |  | 
 | 	val = clamp_val(val, L2_CTL_STALL_MIN, L2_CTL_STALL_MAX); | 
 |  | 
 | 	switch (id) { | 
 | 	case L2_WS_STALL: | 
 | 		data = FIELD_PREP(L2_CTL_WS_STALL_MASK, val); | 
 | 		mask = L2_CTL_WS_STALL_MASK; | 
 | 		break; | 
 | 	case L2_TAG_STALL: | 
 | 		data = FIELD_PREP(L2_CTL_TAG_STALL_MASK, val); | 
 | 		mask = L2_CTL_TAG_STALL_MASK; | 
 | 		break; | 
 | 	case L2_DATA_STALL: | 
 | 		data = FIELD_PREP(L2_CTL_DATA_STALL_MASK, val); | 
 | 		mask = L2_CTL_DATA_STALL_MASK; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	data |= L2_CTL_SET_CLKRATIO; | 
 | 	mask |= L2_CTL_SET_CLKRATIO; | 
 |  | 
 | 	ret = regmap_update_bits(l2->sys_regs, L2_CTL_REG, mask, data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return regmap_read_poll_timeout(l2->sys_regs, L2_CTL_REG, data, | 
 | 					data & L2_CTL_CLKRATIO_LOCK, | 
 | 					L2_CTL_STALL_SET_DELAY_US, | 
 | 					L2_CTL_STALL_SET_TOUT_US); | 
 | } | 
 |  | 
 | static void l2_ctl_clear_data(void *data) | 
 | { | 
 | 	struct l2_ctl *l2 = data; | 
 | 	struct platform_device *pdev = to_platform_device(l2->dev); | 
 |  | 
 | 	platform_set_drvdata(pdev, NULL); | 
 | } | 
 |  | 
 | static struct l2_ctl *l2_ctl_create_data(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct l2_ctl *l2; | 
 | 	int ret; | 
 |  | 
 | 	l2 = devm_kzalloc(dev, sizeof(*l2), GFP_KERNEL); | 
 | 	if (!l2) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	ret = devm_add_action(dev, l2_ctl_clear_data, l2); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "Can't add L2 CTL data clear action\n"); | 
 | 		return ERR_PTR(ret); | 
 | 	} | 
 |  | 
 | 	l2->dev = dev; | 
 | 	platform_set_drvdata(pdev, l2); | 
 |  | 
 | 	return l2; | 
 | } | 
 |  | 
 | static int l2_ctl_find_sys_regs(struct l2_ctl *l2) | 
 | { | 
 | 	l2->sys_regs = syscon_node_to_regmap(l2->dev->of_node->parent); | 
 | 	if (IS_ERR(l2->sys_regs)) { | 
 | 		dev_err(l2->dev, "Couldn't get L2 CTL register map\n"); | 
 | 		return PTR_ERR(l2->sys_regs); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int l2_ctl_of_parse_property(struct l2_ctl *l2, enum l2_ctl_stall id, | 
 | 				    const char *propname) | 
 | { | 
 | 	int ret = 0; | 
 | 	u32 data; | 
 |  | 
 | 	if (!of_property_read_u32(l2->dev->of_node, propname, &data)) { | 
 | 		ret = l2_ctl_set_latency(l2, id, data); | 
 | 		if (ret) | 
 | 			dev_err(l2->dev, "Invalid value of '%s'\n", propname); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int l2_ctl_of_parse(struct l2_ctl *l2) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = l2_ctl_of_parse_property(l2, L2_WS_STALL, "baikal,l2-ws-latency"); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = l2_ctl_of_parse_property(l2, L2_TAG_STALL, "baikal,l2-tag-latency"); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return l2_ctl_of_parse_property(l2, L2_DATA_STALL, | 
 | 					"baikal,l2-data-latency"); | 
 | } | 
 |  | 
 | static ssize_t l2_ctl_latency_show(struct device *dev, | 
 | 				   struct device_attribute *attr, | 
 | 				   char *buf) | 
 | { | 
 | 	struct l2_ctl_device_attribute *devattr = to_l2_ctl_dev_attr(attr); | 
 | 	struct l2_ctl *l2 = dev_get_drvdata(dev); | 
 | 	u32 data; | 
 | 	int ret; | 
 |  | 
 | 	ret = l2_ctl_get_latency(l2, devattr->id, &data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return scnprintf(buf, PAGE_SIZE, "%u\n", data); | 
 | } | 
 |  | 
 | static ssize_t l2_ctl_latency_store(struct device *dev, | 
 | 				    struct device_attribute *attr, | 
 | 				    const char *buf, size_t count) | 
 | { | 
 | 	struct l2_ctl_device_attribute *devattr = to_l2_ctl_dev_attr(attr); | 
 | 	struct l2_ctl *l2 = dev_get_drvdata(dev); | 
 | 	u32 data; | 
 | 	int ret; | 
 |  | 
 | 	if (kstrtouint(buf, 0, &data) < 0) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = l2_ctl_set_latency(l2, devattr->id, data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static L2_CTL_ATTR_RW(l2_ws_latency, l2_ctl_latency, L2_WS_STALL); | 
 | static L2_CTL_ATTR_RW(l2_tag_latency, l2_ctl_latency, L2_TAG_STALL); | 
 | static L2_CTL_ATTR_RW(l2_data_latency, l2_ctl_latency, L2_DATA_STALL); | 
 |  | 
 | static struct attribute *l2_ctl_sysfs_attrs[] = { | 
 | 	&l2_ctl_attr_l2_ws_latency.dev_attr.attr, | 
 | 	&l2_ctl_attr_l2_tag_latency.dev_attr.attr, | 
 | 	&l2_ctl_attr_l2_data_latency.dev_attr.attr, | 
 | 	NULL | 
 | }; | 
 | ATTRIBUTE_GROUPS(l2_ctl_sysfs); | 
 |  | 
 | static void l2_ctl_remove_sysfs(void *data) | 
 | { | 
 | 	struct l2_ctl *l2 = data; | 
 |  | 
 | 	device_remove_groups(l2->dev, l2_ctl_sysfs_groups); | 
 | } | 
 |  | 
 | static int l2_ctl_init_sysfs(struct l2_ctl *l2) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = device_add_groups(l2->dev, l2_ctl_sysfs_groups); | 
 | 	if (ret) { | 
 | 		dev_err(l2->dev, "Failed to create L2 CTL sysfs nodes\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	ret = devm_add_action_or_reset(l2->dev, l2_ctl_remove_sysfs, l2); | 
 | 	if (ret) | 
 | 		dev_err(l2->dev, "Can't add L2 CTL sysfs remove action\n"); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int l2_ctl_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct l2_ctl *l2; | 
 | 	int ret; | 
 |  | 
 | 	l2 = l2_ctl_create_data(pdev); | 
 | 	if (IS_ERR(l2)) | 
 | 		return PTR_ERR(l2); | 
 |  | 
 | 	ret = l2_ctl_find_sys_regs(l2); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = l2_ctl_of_parse(l2); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = l2_ctl_init_sysfs(l2); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct of_device_id l2_ctl_of_match[] = { | 
 | 	{ .compatible = "baikal,bt1-l2-ctl" }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, l2_ctl_of_match); | 
 |  | 
 | static struct platform_driver l2_ctl_driver = { | 
 | 	.probe = l2_ctl_probe, | 
 | 	.driver = { | 
 | 		.name = "bt1-l2-ctl", | 
 | 		.of_match_table = l2_ctl_of_match | 
 | 	} | 
 | }; | 
 | module_platform_driver(l2_ctl_driver); | 
 |  | 
 | MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>"); | 
 | MODULE_DESCRIPTION("Baikal-T1 L2-cache driver"); |