1// SPDX-License-Identifier: GPL-2.0+
2
3#include "loongson_i2c.h"
4#include "loongson_drv.h"
5#include "loongson_vbios.h"
6
7u32 ls7a_mm_rreg(struct loongson_drm_device *ldev, u32 offset)
8{
9	return readl(ldev->mmio + offset);
10}
11
12void ls7a_mm_wreg(struct loongson_drm_device *ldev, u32 offset, u32 val)
13{
14	writel(val, ldev->mmio + offset);
15}
16
17static inline void __dc_gpio_set_dir(struct loongson_drm_device *ldev,
18				     unsigned int pin, int input)
19{
20	u32 temp;
21
22	temp = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_CFG_OFFSET);
23	if (input)
24		temp |= 1UL << pin;
25	else
26		temp &= ~(1UL << pin);
27	ls7a_mm_wreg(ldev, LS7A_DC_GPIO_CFG_OFFSET, temp);
28}
29
30static void __dc_gpio_set_val(struct loongson_drm_device *ldev, unsigned int pin,
31			      int high)
32{
33	u32 temp;
34
35	temp = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_OUT_OFFSET);
36	if (high)
37		temp |= 1UL << pin;
38	else
39		temp &= ~(1UL << pin);
40	ls7a_mm_wreg(ldev, LS7A_DC_GPIO_OUT_OFFSET, temp);
41}
42
43static void loongson_i2c_set_data(void *i2c, int value)
44{
45	struct loongson_i2c *li2c = i2c;
46	struct loongson_drm_device *ldev = li2c->ldev;
47	unsigned int pin = li2c->data;
48
49	if (value)
50		__dc_gpio_set_dir(ldev, pin, 1);
51	else {
52		__dc_gpio_set_val(ldev, pin, 0);
53		__dc_gpio_set_dir(ldev, pin, 0);
54	}
55}
56
57static void loongson_i2c_set_clock(void *i2c, int value)
58{
59	struct loongson_i2c *li2c = i2c;
60	struct loongson_drm_device *ldev = li2c->ldev;
61	unsigned int pin = li2c->clock;
62
63	if (value)
64		__dc_gpio_set_dir(ldev, pin, 1);
65	else {
66		__dc_gpio_set_val(ldev, pin, 0);
67		__dc_gpio_set_dir(ldev, pin, 0);
68	}
69}
70
71static int loongson_i2c_get_data(void *i2c)
72{
73	int val;
74	struct loongson_i2c *li2c = i2c;
75	struct loongson_drm_device *ldev = li2c->ldev;
76	unsigned int pin = li2c->data;
77
78	val = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_IN_OFFSET);
79
80	return (val >> pin) & 1;
81}
82
83static int loongson_i2c_get_clock(void *i2c)
84{
85	int val;
86	struct loongson_i2c *li2c = i2c;
87	struct loongson_drm_device *ldev = li2c->ldev;
88	unsigned int pin = li2c->clock;
89
90	val = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_IN_OFFSET);
91
92	return (val >> pin) & 1;
93}
94
95static int loongson_i2c_create(struct loongson_drm_device *ldev,
96			       struct loongson_i2c *li2c, const char *name)
97{
98	int ret;
99	unsigned int i2c_num;
100	struct i2c_adapter *i2c_adapter;
101	struct i2c_algo_bit_data *i2c_algo_data;
102
103	i2c_num = li2c->i2c_id;
104	i2c_adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
105	if (!i2c_adapter)
106		return -ENOMEM;
107
108	i2c_algo_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);
109	if (!i2c_algo_data) {
110		ret = -ENOMEM;
111		goto free_adapter;
112	}
113
114	i2c_adapter->owner = THIS_MODULE;
115	i2c_adapter->class = I2C_CLASS_DDC;
116	i2c_adapter->algo_data = i2c_algo_data;
117	i2c_adapter->dev.parent = ldev->dev->dev;
118	i2c_adapter->nr = -1;
119	snprintf(i2c_adapter->name, sizeof(i2c_adapter->name), "%s%d", name,
120		 i2c_num);
121
122	li2c->data = i2c_num * 2;
123	li2c->clock = i2c_num * 2 + 1;
124	DRM_INFO("Created dc-i2c%d, sda=%d, scl=%d\n", i2c_num, li2c->data,
125		 li2c->clock);
126
127	i2c_algo_data->setsda = loongson_i2c_set_data;
128	i2c_algo_data->setscl = loongson_i2c_set_clock;
129	i2c_algo_data->getsda = loongson_i2c_get_data;
130	i2c_algo_data->getscl = loongson_i2c_get_clock;
131	i2c_algo_data->udelay = DC_I2C_TON;
132	i2c_algo_data->timeout = usecs_to_jiffies(2200); /* from VESA */
133
134	ret = i2c_bit_add_numbered_bus(i2c_adapter);
135	if (ret)
136		goto free_algo_data;
137
138	li2c->adapter = i2c_adapter;
139	i2c_algo_data->data = li2c;
140	i2c_set_adapdata(li2c->adapter, li2c);
141	li2c->init = true;
142	li2c->ldev = ldev;
143	DRM_INFO("Register i2c algo-bit adapter [%s]\n", i2c_adapter->name);
144
145	return 0;
146
147free_algo_data:
148	DRM_ERROR("Failed to register i2c adapter %s\n", i2c_adapter->name);
149	kfree(i2c_algo_data);
150free_adapter:
151	kfree(i2c_adapter);
152
153	return ret;
154}
155
156int loongson_gpio_init(struct loongson_drm_device *ldev)
157{
158	int pin;
159
160	/* set gpio dir output 0-3 */
161	for (pin = 0; pin < 4; pin++) {
162		__dc_gpio_set_val(ldev, pin, 0);
163		__dc_gpio_set_dir(ldev, pin, 0);
164	}
165
166	return 0;
167}
168
169int loongson_i2c_init(struct loongson_drm_device *ldev)
170{
171	int ret, i;
172
173	ret = get_loongson_i2c(ldev);
174	if (ret != true) {
175		DRM_ERROR("Failed to get i2c_id form vbios\n");
176		return -ENODEV;
177	}
178
179	for (i = 0; i < DC_I2C_BUS_MAX; i++) {
180		if (!ldev->i2c_bus[i].use)
181			continue;
182		ldev->i2c_bus[i].i2c_id = i;
183		ret = loongson_i2c_create(ldev, &ldev->i2c_bus[i], DC_I2C_NAME);
184		if (ret)
185			return ret;
186	}
187
188	return 0;
189}
190