summaryrefslogtreecommitdiff
path: root/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c
blob: cd452cc20fea8f96737b1b387a1e52efb49531d0 (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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/pm_qos.h>
#include <linux/delay.h>

/* G-Min addition: "platform_is()" lives in intel_mid_pm.h in the MCG
 * tree, but it's just platform ID info and we don't want to pull in
 * the whole SFI-based PM architecture.
 */
#define INTEL_ATOM_MRST 0x26
#define INTEL_ATOM_MFLD 0x27
#define INTEL_ATOM_CLV 0x35
#define INTEL_ATOM_MRFLD 0x4a
#define INTEL_ATOM_BYT 0x37
#define INTEL_ATOM_MOORFLD 0x5a
#define INTEL_ATOM_CHT 0x4c
/* synchronization for sharing the I2C controller */
#define PUNIT_PORT	0x04
#define PUNIT_DOORBELL_OPCODE	(0xE0)
#define PUNIT_DOORBELL_REG	(0x0)
#ifndef CSTATE_EXIT_LATENCY
#define CSTATE_EXIT_LATENCY_C1 1
#endif
static inline int platform_is(u8 model)
{
	return (boot_cpu_data.x86_model == model);
}

#include "../../include/asm/intel_mid_pcihelpers.h"

/* Unified message bus read/write operation */
static DEFINE_SPINLOCK(msgbus_lock);

static struct pci_dev *pci_root;
static struct pm_qos_request pm_qos;

#define DW_I2C_NEED_QOS	(platform_is(INTEL_ATOM_BYT))

static int intel_mid_msgbus_init(void)
{
	pci_root = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0));
	if (!pci_root) {
		pr_err("%s: Error: msgbus PCI handle NULL\n", __func__);
		return -ENODEV;
	}

	if (DW_I2C_NEED_QOS) {
		pm_qos_add_request(&pm_qos,
			PM_QOS_CPU_DMA_LATENCY,
			PM_QOS_DEFAULT_VALUE);
	}
	return 0;
}
fs_initcall(intel_mid_msgbus_init);

u32 intel_mid_msgbus_read32_raw(u32 cmd)
{
	unsigned long irq_flags;
	u32 data;

	spin_lock_irqsave(&msgbus_lock, irq_flags);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);

	return data;
}
EXPORT_SYMBOL(intel_mid_msgbus_read32_raw);

/*
 * GU: this function is only used by the VISA and 'VXD' drivers.
 */
u32 intel_mid_msgbus_read32_raw_ext(u32 cmd, u32 cmd_ext)
{
	unsigned long irq_flags;
	u32 data;

	spin_lock_irqsave(&msgbus_lock, irq_flags);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, cmd_ext);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);

	return data;
}
EXPORT_SYMBOL(intel_mid_msgbus_read32_raw_ext);

void intel_mid_msgbus_write32_raw(u32 cmd, u32 data)
{
	unsigned long irq_flags;

	spin_lock_irqsave(&msgbus_lock, irq_flags);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);
}
EXPORT_SYMBOL(intel_mid_msgbus_write32_raw);

/*
 * GU: this function is only used by the VISA and 'VXD' drivers.
 */
void intel_mid_msgbus_write32_raw_ext(u32 cmd, u32 cmd_ext, u32 data)
{
	unsigned long irq_flags;

	spin_lock_irqsave(&msgbus_lock, irq_flags);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG, cmd_ext);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);
}
EXPORT_SYMBOL(intel_mid_msgbus_write32_raw_ext);

u32 intel_mid_msgbus_read32(u8 port, u32 addr)
{
	unsigned long irq_flags;
	u32 data;
	u32 cmd;
	u32 cmdext;

	cmd = (PCI_ROOT_MSGBUS_READ << 24) | (port << 16) |
		((addr & 0xff) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE;
	cmdext = addr & 0xffffff00;

	spin_lock_irqsave(&msgbus_lock, irq_flags);

	if (cmdext) {
		/* This resets to 0 automatically, no need to write 0 */
		pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG,
					cmdext);
	}

	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	pci_read_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, &data);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);

	return data;
}
EXPORT_SYMBOL(intel_mid_msgbus_read32);

void intel_mid_msgbus_write32(u8 port, u32 addr, u32 data)
{
	unsigned long irq_flags;
	u32 cmd;
	u32 cmdext;

	cmd = (PCI_ROOT_MSGBUS_WRITE << 24) | (port << 16) |
		((addr & 0xFF) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE;
	cmdext = addr & 0xffffff00;

	spin_lock_irqsave(&msgbus_lock, irq_flags);
	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_DATA_REG, data);

	if (cmdext) {
		/* This resets to 0 automatically, no need to write 0 */
		pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_EXT_REG,
					cmdext);
	}

	pci_write_config_dword(pci_root, PCI_ROOT_MSGBUS_CTRL_REG, cmd);
	spin_unlock_irqrestore(&msgbus_lock, irq_flags);
}
EXPORT_SYMBOL(intel_mid_msgbus_write32);

/* called only from where is later then fs_initcall */
u32 intel_mid_soc_stepping(void)
{
	return pci_root->revision;
}
EXPORT_SYMBOL(intel_mid_soc_stepping);

static bool is_south_complex_device(struct pci_dev *dev)
{
	unsigned int base_class = dev->class >> 16;
	unsigned int sub_class  = (dev->class & SUB_CLASS_MASK) >> 8;

	/* other than camera, pci bridges and display,
	 * everything else are south complex devices.
	 */
	if (((base_class == PCI_BASE_CLASS_MULTIMEDIA) &&
	     (sub_class == ISP_SUB_CLASS)) ||
	    (base_class == PCI_BASE_CLASS_BRIDGE) ||
	    ((base_class == PCI_BASE_CLASS_DISPLAY) && !sub_class))
		return false;
	else
		return true;
}

/* In BYT platform, d3_delay for internal south complex devices,
 * they are not subject to 10 ms d3 to d0 delay required by pci spec.
 */
static void pci_d3_delay_fixup(struct pci_dev *dev)
{
	if (platform_is(INTEL_ATOM_BYT) ||
		platform_is(INTEL_ATOM_CHT)) {
		/* All internal devices are in bus 0. */
		if (dev->bus->number == 0 && is_south_complex_device(dev)) {
			dev->d3_delay = INTERNAL_PCI_PM_D3_WAIT;
			dev->d3cold_delay = INTERNAL_PCI_PM_D3_WAIT;
		}
	}
}
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_d3_delay_fixup);

#define PUNIT_SEMAPHORE	(platform_is(INTEL_ATOM_BYT) ? 0x7 : 0x10E)
#define GET_SEM() (intel_mid_msgbus_read32(PUNIT_PORT, PUNIT_SEMAPHORE) & 0x1)

static void reset_semaphore(void)
{
	u32 data;

	data = intel_mid_msgbus_read32(PUNIT_PORT, PUNIT_SEMAPHORE);
	smp_mb();
	data = data & 0xfffffffc;
	intel_mid_msgbus_write32(PUNIT_PORT, PUNIT_SEMAPHORE, data);
	smp_mb();

}

int intel_mid_dw_i2c_acquire_ownership(void)
{
	u32 ret = 0;
	u32 data = 0; /* data sent to PUNIT */
	u32 cmd;
	u32 cmdext;
	int timeout = 1000;

	if (DW_I2C_NEED_QOS)
		pm_qos_update_request(&pm_qos, CSTATE_EXIT_LATENCY_C1 - 1);

	/*
	 * We need disable irq. Otherwise, the main thread
	 * might be preempted and the other thread jumps to
	 * disable irq for a long time. Another case is
	 * some irq handlers might trigger power voltage change
	 */
	BUG_ON(irqs_disabled());
	local_irq_disable();

	/* host driver writes 0x2 to side band register 0x7 */
	intel_mid_msgbus_write32(PUNIT_PORT, PUNIT_SEMAPHORE, 0x2);
	smp_mb();

	/* host driver sends 0xE0 opcode to PUNIT and writes 0 register */
	cmd = (PUNIT_DOORBELL_OPCODE << 24) | (PUNIT_PORT << 16) |
	((PUNIT_DOORBELL_REG & 0xFF) << 8) | PCI_ROOT_MSGBUS_DWORD_ENABLE;
	cmdext = PUNIT_DOORBELL_REG & 0xffffff00;

	if (cmdext)
		intel_mid_msgbus_write32_raw_ext(cmd, cmdext, data);
	else
		intel_mid_msgbus_write32_raw(cmd, data);

	/* host driver waits for bit 0 to be set in side band 0x7 */
	while (GET_SEM() != 0x1) {
		udelay(100);
		timeout--;
		if (timeout <= 0) {
			pr_err("Timeout: semaphore timed out, reset sem\n");
			ret = -ETIMEDOUT;
			reset_semaphore();
			/*Delay 1ms in case race with punit*/
			udelay(1000);
			if (GET_SEM() != 0) {
				/*Reset again as kernel might race with punit*/
				reset_semaphore();
			}
			pr_err("PUNIT SEM: %d\n",
					intel_mid_msgbus_read32(PUNIT_PORT,
						PUNIT_SEMAPHORE));
			local_irq_enable();

			if (DW_I2C_NEED_QOS) {
				pm_qos_update_request(&pm_qos,
					 PM_QOS_DEFAULT_VALUE);
			}

			return ret;
		}
	}
	smp_mb();

	return ret;
}
EXPORT_SYMBOL(intel_mid_dw_i2c_acquire_ownership);

int intel_mid_dw_i2c_release_ownership(void)
{
	reset_semaphore();
	local_irq_enable();

	if (DW_I2C_NEED_QOS)
		pm_qos_update_request(&pm_qos, PM_QOS_DEFAULT_VALUE);

	return 0;
}
EXPORT_SYMBOL(intel_mid_dw_i2c_release_ownership);