1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright 2024 NXP
4  */
5 
6 #include <dt-bindings/reset/imx8mp-reset-audiomix.h>
7 
8 #include <linux/auxiliary_bus.h>
9 #include <linux/device.h>
10 #include <linux/io.h>
11 #include <linux/module.h>
12 #include <linux/of.h>
13 #include <linux/of_address.h>
14 #include <linux/reset-controller.h>
15 
16 #define IMX8MP_AUDIOMIX_EARC_RESET_OFFSET	0x200
17 #define IMX8MP_AUDIOMIX_EARC_RESET_MASK		BIT(1)
18 #define IMX8MP_AUDIOMIX_EARC_PHY_RESET_MASK	BIT(2)
19 
20 #define IMX8MP_AUDIOMIX_DSP_RUNSTALL_OFFSET	0x108
21 #define IMX8MP_AUDIOMIX_DSP_RUNSTALL_MASK	BIT(5)
22 
23 struct imx8mp_reset_map {
24 	unsigned int offset;
25 	unsigned int mask;
26 	bool active_low;
27 };
28 
29 static const struct imx8mp_reset_map reset_map[] = {
30 	[IMX8MP_AUDIOMIX_EARC_RESET] = {
31 		.offset	= IMX8MP_AUDIOMIX_EARC_RESET_OFFSET,
32 		.mask	= IMX8MP_AUDIOMIX_EARC_RESET_MASK,
33 		.active_low = true,
34 	},
35 	[IMX8MP_AUDIOMIX_EARC_PHY_RESET] = {
36 		.offset	= IMX8MP_AUDIOMIX_EARC_RESET_OFFSET,
37 		.mask	= IMX8MP_AUDIOMIX_EARC_PHY_RESET_MASK,
38 		.active_low = true,
39 	},
40 	[IMX8MP_AUDIOMIX_DSP_RUNSTALL] = {
41 		.offset	= IMX8MP_AUDIOMIX_DSP_RUNSTALL_OFFSET,
42 		.mask	= IMX8MP_AUDIOMIX_DSP_RUNSTALL_MASK,
43 		.active_low = false,
44 	},
45 };
46 
47 struct imx8mp_audiomix_reset {
48 	struct reset_controller_dev rcdev;
49 	spinlock_t lock; /* protect register read-modify-write cycle */
50 	void __iomem *base;
51 };
52 
to_imx8mp_audiomix_reset(struct reset_controller_dev * rcdev)53 static struct imx8mp_audiomix_reset *to_imx8mp_audiomix_reset(struct reset_controller_dev *rcdev)
54 {
55 	return container_of(rcdev, struct imx8mp_audiomix_reset, rcdev);
56 }
57 
imx8mp_audiomix_update(struct reset_controller_dev * rcdev,unsigned long id,bool assert)58 static int imx8mp_audiomix_update(struct reset_controller_dev *rcdev,
59 				  unsigned long id, bool assert)
60 {
61 	struct imx8mp_audiomix_reset *priv = to_imx8mp_audiomix_reset(rcdev);
62 	void __iomem *reg_addr = priv->base;
63 	unsigned int mask, offset, active_low;
64 	unsigned long reg, flags;
65 
66 	mask = reset_map[id].mask;
67 	offset = reset_map[id].offset;
68 	active_low = reset_map[id].active_low;
69 
70 	spin_lock_irqsave(&priv->lock, flags);
71 
72 	reg = readl(reg_addr + offset);
73 	if (active_low ^ assert)
74 		reg |= mask;
75 	else
76 		reg &= ~mask;
77 	writel(reg, reg_addr + offset);
78 
79 	spin_unlock_irqrestore(&priv->lock, flags);
80 
81 	return 0;
82 }
83 
imx8mp_audiomix_reset_assert(struct reset_controller_dev * rcdev,unsigned long id)84 static int imx8mp_audiomix_reset_assert(struct reset_controller_dev *rcdev,
85 					unsigned long id)
86 {
87 	return imx8mp_audiomix_update(rcdev, id, true);
88 }
89 
imx8mp_audiomix_reset_deassert(struct reset_controller_dev * rcdev,unsigned long id)90 static int imx8mp_audiomix_reset_deassert(struct reset_controller_dev *rcdev,
91 					  unsigned long id)
92 {
93 	return imx8mp_audiomix_update(rcdev, id, false);
94 }
95 
96 static const struct reset_control_ops imx8mp_audiomix_reset_ops = {
97 	.assert   = imx8mp_audiomix_reset_assert,
98 	.deassert = imx8mp_audiomix_reset_deassert,
99 };
100 
imx8mp_audiomix_reset_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)101 static int imx8mp_audiomix_reset_probe(struct auxiliary_device *adev,
102 				       const struct auxiliary_device_id *id)
103 {
104 	struct imx8mp_audiomix_reset *priv;
105 	struct device *dev = &adev->dev;
106 	int ret;
107 
108 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
109 	if (!priv)
110 		return -ENOMEM;
111 
112 	spin_lock_init(&priv->lock);
113 
114 	priv->rcdev.owner     = THIS_MODULE;
115 	priv->rcdev.nr_resets = ARRAY_SIZE(reset_map);
116 	priv->rcdev.ops       = &imx8mp_audiomix_reset_ops;
117 	priv->rcdev.of_node   = dev->parent->of_node;
118 	priv->rcdev.dev	      = dev;
119 	priv->rcdev.of_reset_n_cells = 1;
120 	priv->base            = of_iomap(dev->parent->of_node, 0);
121 	if (!priv->base)
122 		return -ENOMEM;
123 
124 	dev_set_drvdata(dev, priv);
125 
126 	ret = devm_reset_controller_register(dev, &priv->rcdev);
127 	if (ret)
128 		goto out_unmap;
129 
130 	return 0;
131 
132 out_unmap:
133 	iounmap(priv->base);
134 	return ret;
135 }
136 
imx8mp_audiomix_reset_remove(struct auxiliary_device * adev)137 static void imx8mp_audiomix_reset_remove(struct auxiliary_device *adev)
138 {
139 	struct imx8mp_audiomix_reset *priv = dev_get_drvdata(&adev->dev);
140 
141 	iounmap(priv->base);
142 }
143 
144 static const struct auxiliary_device_id imx8mp_audiomix_reset_ids[] = {
145 	{
146 		.name = "clk_imx8mp_audiomix.reset",
147 	},
148 	{ }
149 };
150 MODULE_DEVICE_TABLE(auxiliary, imx8mp_audiomix_reset_ids);
151 
152 static struct auxiliary_driver imx8mp_audiomix_reset_driver = {
153 	.probe		= imx8mp_audiomix_reset_probe,
154 	.remove		= imx8mp_audiomix_reset_remove,
155 	.id_table	= imx8mp_audiomix_reset_ids,
156 };
157 
158 module_auxiliary_driver(imx8mp_audiomix_reset_driver);
159 
160 MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
161 MODULE_DESCRIPTION("Freescale i.MX8MP Audio Block Controller reset driver");
162 MODULE_LICENSE("GPL");
163