usbmisc_imx.c 8.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * Copyright 2012 Freescale Semiconductor, Inc.
 *
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/err.h>
#include <linux/io.h>
16
#include <linux/delay.h>
17

18
#include "ci_hdrc_imx.h"
19

20 21 22
#define MX25_USB_PHY_CTRL_OFFSET	0x08
#define MX25_BM_EXTERNAL_VBUS_DIVIDER	BIT(23)

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#define MX25_EHCI_INTERFACE_SINGLE_UNI	(2 << 0)
#define MX25_EHCI_INTERFACE_DIFF_UNI	(0 << 0)
#define MX25_EHCI_INTERFACE_MASK	(0xf)

#define MX25_OTG_SIC_SHIFT		29
#define MX25_OTG_SIC_MASK		(0x3 << MX25_OTG_SIC_SHIFT)
#define MX25_OTG_PM_BIT			BIT(24)
#define MX25_OTG_PP_BIT			BIT(11)
#define MX25_OTG_OCPOL_BIT		BIT(3)

#define MX25_H1_SIC_SHIFT		21
#define MX25_H1_SIC_MASK		(0x3 << MX25_H1_SIC_SHIFT)
#define MX25_H1_PP_BIT			BIT(18)
#define MX25_H1_PM_BIT			BIT(16)
#define MX25_H1_IPPUE_UP_BIT		BIT(7)
#define MX25_H1_IPPUE_DOWN_BIT		BIT(6)
#define MX25_H1_TLL_BIT			BIT(5)
#define MX25_H1_USBTE_BIT		BIT(4)
#define MX25_H1_OCPOL_BIT		BIT(2)

43 44 45 46
#define MX27_H1_PM_BIT			BIT(8)
#define MX27_H2_PM_BIT			BIT(16)
#define MX27_OTG_PM_BIT			BIT(24)

47
#define MX53_USB_OTG_PHY_CTRL_0_OFFSET	0x08
48
#define MX53_USB_OTG_PHY_CTRL_1_OFFSET	0x0c
49 50 51 52 53
#define MX53_USB_UH2_CTRL_OFFSET	0x14
#define MX53_USB_UH3_CTRL_OFFSET	0x18
#define MX53_BM_OVER_CUR_DIS_H1		BIT(5)
#define MX53_BM_OVER_CUR_DIS_OTG	BIT(8)
#define MX53_BM_OVER_CUR_DIS_UHx	BIT(30)
54 55
#define MX53_USB_PHYCTRL1_PLLDIV_MASK	0x3
#define MX53_USB_PLL_DIV_24_MHZ		0x01
56

57
#define MX6_BM_OVER_CUR_DIS		BIT(7)
58

59 60
#define VF610_OVER_CUR_DIS		BIT(7)

61 62 63 64 65 66 67
struct usbmisc_ops {
	/* It's called once when probe a usb device */
	int (*init)(struct imx_usbmisc_data *data);
	/* It's called once after adding a usb device */
	int (*post)(struct imx_usbmisc_data *data);
};

68
struct imx_usbmisc {
69 70
	void __iomem *base;
	spinlock_t lock;
71
	const struct usbmisc_ops *ops;
72 73
};

74 75
static int usbmisc_imx25_init(struct imx_usbmisc_data *data)
{
76
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
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
	unsigned long flags;
	u32 val = 0;

	if (data->index > 1)
		return -EINVAL;

	spin_lock_irqsave(&usbmisc->lock, flags);
	switch (data->index) {
	case 0:
		val = readl(usbmisc->base);
		val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT);
		val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT;
		val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT);
		writel(val, usbmisc->base);
		break;
	case 1:
		val = readl(usbmisc->base);
		val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT |  MX25_H1_IPPUE_UP_BIT);
		val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT;
		val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT |
			MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT);

		writel(val, usbmisc->base);

		break;
	}
	spin_unlock_irqrestore(&usbmisc->lock, flags);

	return 0;
}

108
static int usbmisc_imx25_post(struct imx_usbmisc_data *data)
109
{
110
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
111 112 113 114
	void __iomem *reg;
	unsigned long flags;
	u32 val;

115 116
	if (data->index > 2)
		return -EINVAL;
117

118
	if (data->evdo) {
119
		spin_lock_irqsave(&usbmisc->lock, flags);
120
		reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET;
121 122 123 124 125 126 127 128 129
		val = readl(reg);
		writel(val | MX25_BM_EXTERNAL_VBUS_DIVIDER, reg);
		spin_unlock_irqrestore(&usbmisc->lock, flags);
		usleep_range(5000, 10000); /* needed to stabilize voltage */
	}

	return 0;
}

130 131
static int usbmisc_imx27_init(struct imx_usbmisc_data *data)
{
132
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
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
	unsigned long flags;
	u32 val;

	switch (data->index) {
	case 0:
		val = MX27_OTG_PM_BIT;
		break;
	case 1:
		val = MX27_H1_PM_BIT;
		break;
	case 2:
		val = MX27_H2_PM_BIT;
		break;
	default:
		return -EINVAL;
	};

	spin_lock_irqsave(&usbmisc->lock, flags);
	if (data->disable_oc)
		val = readl(usbmisc->base) | val;
	else
		val = readl(usbmisc->base) & ~val;
	writel(val, usbmisc->base);
	spin_unlock_irqrestore(&usbmisc->lock, flags);

	return 0;
}

161
static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
162
{
163
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
164 165 166 167
	void __iomem *reg = NULL;
	unsigned long flags;
	u32 val = 0;

168 169
	if (data->index > 3)
		return -EINVAL;
170

171
	/* Select a 24 MHz reference clock for the PHY  */
172
	val = readl(usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET);
173 174 175 176
	val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK;
	val |= MX53_USB_PLL_DIV_24_MHZ;
	writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET);

177
	if (data->disable_oc) {
178
		spin_lock_irqsave(&usbmisc->lock, flags);
179
		switch (data->index) {
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
		case 0:
			reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET;
			val = readl(reg) | MX53_BM_OVER_CUR_DIS_OTG;
			break;
		case 1:
			reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET;
			val = readl(reg) | MX53_BM_OVER_CUR_DIS_H1;
			break;
		case 2:
			reg = usbmisc->base + MX53_USB_UH2_CTRL_OFFSET;
			val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx;
			break;
		case 3:
			reg = usbmisc->base + MX53_USB_UH3_CTRL_OFFSET;
			val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx;
			break;
		}
		if (reg && val)
			writel(val, reg);
		spin_unlock_irqrestore(&usbmisc->lock, flags);
	}

	return 0;
}

205
static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
206
{
207
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
208 209 210
	unsigned long flags;
	u32 reg;

211 212
	if (data->index > 3)
		return -EINVAL;
213

214
	if (data->disable_oc) {
215
		spin_lock_irqsave(&usbmisc->lock, flags);
216
		reg = readl(usbmisc->base + data->index * 4);
217
		writel(reg | MX6_BM_OVER_CUR_DIS,
218
			usbmisc->base + data->index * 4);
219 220 221 222 223 224
		spin_unlock_irqrestore(&usbmisc->lock, flags);
	}

	return 0;
}

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
static int usbmisc_vf610_init(struct imx_usbmisc_data *data)
{
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
	u32 reg;

	/*
	 * Vybrid only has one misc register set, but in two different
	 * areas. These is reflected in two instances of this driver.
	 */
	if (data->index >= 1)
		return -EINVAL;

	if (data->disable_oc) {
		reg = readl(usbmisc->base);
		writel(reg | VF610_OVER_CUR_DIS, usbmisc->base);
	}

	return 0;
}

245
static const struct usbmisc_ops imx25_usbmisc_ops = {
246
	.init = usbmisc_imx25_init,
247 248 249
	.post = usbmisc_imx25_post,
};

250 251 252 253
static const struct usbmisc_ops imx27_usbmisc_ops = {
	.init = usbmisc_imx27_init,
};

254 255 256 257
static const struct usbmisc_ops imx53_usbmisc_ops = {
	.init = usbmisc_imx53_init,
};

258 259 260 261
static const struct usbmisc_ops imx6q_usbmisc_ops = {
	.init = usbmisc_imx6q_init,
};

262 263 264 265
static const struct usbmisc_ops vf610_usbmisc_ops = {
	.init = usbmisc_vf610_init,
};

266 267
int imx_usbmisc_init(struct imx_usbmisc_data *data)
{
268 269
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);

270 271 272 273 274 275 276 277
	if (!usbmisc->ops->init)
		return 0;
	return usbmisc->ops->init(data);
}
EXPORT_SYMBOL_GPL(imx_usbmisc_init);

int imx_usbmisc_init_post(struct imx_usbmisc_data *data)
{
278 279
	struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);

280 281 282 283 284 285
	if (!usbmisc->ops->post)
		return 0;
	return usbmisc->ops->post(data);
}
EXPORT_SYMBOL_GPL(imx_usbmisc_init_post);

286
static const struct of_device_id usbmisc_imx_dt_ids[] = {
287 288 289 290
	{
		.compatible = "fsl,imx25-usbmisc",
		.data = &imx25_usbmisc_ops,
	},
291 292 293 294
	{
		.compatible = "fsl,imx35-usbmisc",
		.data = &imx25_usbmisc_ops,
	},
295 296 297 298
	{
		.compatible = "fsl,imx27-usbmisc",
		.data = &imx27_usbmisc_ops,
	},
299 300 301 302
	{
		.compatible = "fsl,imx51-usbmisc",
		.data = &imx53_usbmisc_ops,
	},
303 304 305 306
	{
		.compatible = "fsl,imx53-usbmisc",
		.data = &imx53_usbmisc_ops,
	},
307 308 309 310
	{
		.compatible = "fsl,imx6q-usbmisc",
		.data = &imx6q_usbmisc_ops,
	},
311 312 313 314
	{
		.compatible = "fsl,vf610-usbmisc",
		.data = &vf610_usbmisc_ops,
	},
315 316
	{ /* sentinel */ }
};
317
MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids);
318

319
static int usbmisc_imx_probe(struct platform_device *pdev)
320 321
{
	struct resource	*res;
322
	struct imx_usbmisc *data;
323
	struct of_device_id *tmp_dev;
324 325 326 327 328 329 330 331

	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	spin_lock_init(&data->lock);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
332 333 334
	data->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(data->base))
		return PTR_ERR(data->base);
335

336 337 338
	tmp_dev = (struct of_device_id *)
		of_match_device(usbmisc_imx_dt_ids, &pdev->dev);
	data->ops = (const struct usbmisc_ops *)tmp_dev->data;
339
	platform_set_drvdata(pdev, data);
340 341 342 343

	return 0;
}

344
static int usbmisc_imx_remove(struct platform_device *pdev)
345 346 347 348
{
	return 0;
}

349 350 351
static struct platform_driver usbmisc_imx_driver = {
	.probe = usbmisc_imx_probe,
	.remove = usbmisc_imx_remove,
352
	.driver = {
353 354
		.name = "usbmisc_imx",
		.of_match_table = usbmisc_imx_dt_ids,
355 356 357
	 },
};

358
module_platform_driver(usbmisc_imx_driver);
359

360
MODULE_ALIAS("platform:usbmisc-imx");
361
MODULE_LICENSE("GPL v2");
362
MODULE_DESCRIPTION("driver for imx usb non-core registers");
363
MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");