summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/loongson/lsdc_i2c.c
blob: 9625d0b1d0b4d81486e62227880b43d3f04c9c63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2023 Loongson Technology Corporation Limited
 */

#include <drm/drm_managed.h>

#include "lsdc_drv.h"
#include "lsdc_output.h"

/*
 * __lsdc_gpio_i2c_set - set the state of a gpio pin indicated by mask
 * @mask: gpio pin mask
 * @state: "0" for low, "1" for high
 */
static void __lsdc_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state)
{
	struct lsdc_device *ldev = to_lsdc(li2c->ddev);
	unsigned long flags;
	u8 val;

	spin_lock_irqsave(&ldev->reglock, flags);

	if (state) {
		/*
		 * Setting this pin as input directly, write 1 for input.
		 * The external pull-up resistor will pull the level up
		 */
		val = readb(li2c->dir_reg);
		val |= mask;
		writeb(val, li2c->dir_reg);
	} else {
		/* First set this pin as output, write 0 for output */
		val = readb(li2c->dir_reg);
		val &= ~mask;
		writeb(val, li2c->dir_reg);

		/* Then, make this pin output 0 */
		val = readb(li2c->dat_reg);
		val &= ~mask;
		writeb(val, li2c->dat_reg);
	}

	spin_unlock_irqrestore(&ldev->reglock, flags);
}

/*
 * __lsdc_gpio_i2c_get - read value back from the gpio pin indicated by mask
 * @mask: gpio pin mask
 * return "0" for low, "1" for high
 */
static int __lsdc_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask)
{
	struct lsdc_device *ldev = to_lsdc(li2c->ddev);
	unsigned long flags;
	u8 val;

	spin_lock_irqsave(&ldev->reglock, flags);

	/* First set this pin as input */
	val = readb(li2c->dir_reg);
	val |= mask;
	writeb(val, li2c->dir_reg);

	/* Then get level state from this pin */
	val = readb(li2c->dat_reg);

	spin_unlock_irqrestore(&ldev->reglock, flags);

	return (val & mask) ? 1 : 0;
}

static void lsdc_gpio_i2c_set_sda(void *i2c, int state)
{
	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
	/* set state on the li2c->sda pin */
	return __lsdc_gpio_i2c_set(li2c, li2c->sda, state);
}

static void lsdc_gpio_i2c_set_scl(void *i2c, int state)
{
	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
	/* set state on the li2c->scl pin */
	return __lsdc_gpio_i2c_set(li2c, li2c->scl, state);
}

static int lsdc_gpio_i2c_get_sda(void *i2c)
{
	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
	/* read value from the li2c->sda pin */
	return __lsdc_gpio_i2c_get(li2c, li2c->sda);
}

static int lsdc_gpio_i2c_get_scl(void *i2c)
{
	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
	/* read the value from the li2c->scl pin */
	return __lsdc_gpio_i2c_get(li2c, li2c->scl);
}

static void lsdc_destroy_i2c(struct drm_device *ddev, void *data)
{
	struct lsdc_i2c *li2c = (struct lsdc_i2c *)data;

	if (li2c) {
		i2c_del_adapter(&li2c->adapter);
		kfree(li2c);
	}
}

/*
 * The DC in ls7a1000/ls7a2000/ls2k2000 has builtin gpio hardware
 *
 * @reg_base: gpio reg base
 * @index: output channel index, 0 for PIPE0, 1 for PIPE1
 */
int lsdc_create_i2c_chan(struct drm_device *ddev,
			 struct lsdc_display_pipe *dispipe,
			 unsigned int index)
{
	struct lsdc_device *ldev = to_lsdc(ddev);
	struct i2c_adapter *adapter;
	struct lsdc_i2c *li2c;
	int ret;

	li2c = kzalloc(sizeof(*li2c), GFP_KERNEL);
	if (!li2c)
		return -ENOMEM;

	dispipe->li2c = li2c;

	if (index == 0) {
		li2c->sda = 0x01;  /* pin 0 */
		li2c->scl = 0x02;  /* pin 1 */
	} else if (index == 1) {
		li2c->sda = 0x04;  /* pin 2 */
		li2c->scl = 0x08;  /* pin 3 */
	} else {
		return -ENOENT;
	}

	li2c->ddev = ddev;
	li2c->dir_reg = ldev->reg_base + LS7A_DC_GPIO_DIR_REG;
	li2c->dat_reg = ldev->reg_base + LS7A_DC_GPIO_DAT_REG;

	li2c->bit.setsda = lsdc_gpio_i2c_set_sda;
	li2c->bit.setscl = lsdc_gpio_i2c_set_scl;
	li2c->bit.getsda = lsdc_gpio_i2c_get_sda;
	li2c->bit.getscl = lsdc_gpio_i2c_get_scl;
	li2c->bit.udelay = 5;
	li2c->bit.timeout = usecs_to_jiffies(2200);
	li2c->bit.data = li2c;

	adapter = &li2c->adapter;
	adapter->algo_data = &li2c->bit;
	adapter->owner = THIS_MODULE;
	adapter->class = I2C_CLASS_DDC;
	adapter->dev.parent = ddev->dev;
	adapter->nr = -1;

	snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", index);

	i2c_set_adapdata(adapter, li2c);

	ret = i2c_bit_add_bus(adapter);
	if (ret) {
		kfree(li2c);
		return ret;
	}

	ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c);
	if (ret)
		return ret;

	drm_info(ddev, "%s(sda pin mask=%u, scl pin mask=%u) created\n",
		 adapter->name, li2c->sda, li2c->scl);

	return 0;
}