18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/**
38c2ecf20Sopenharmony_ci * tusb1210.c - TUSB1210 USB ULPI PHY driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2015 Intel Corporation
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/ulpi/driver.h>
118c2ecf20Sopenharmony_ci#include <linux/ulpi/regs.h>
128c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
138c2ecf20Sopenharmony_ci#include <linux/phy/ulpi_phy.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define TUSB1210_VENDOR_SPECIFIC2		0x80
168c2ecf20Sopenharmony_ci#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT	0
178c2ecf20Sopenharmony_ci#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT	4
188c2ecf20Sopenharmony_ci#define TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT	6
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistruct tusb1210 {
218c2ecf20Sopenharmony_ci	struct ulpi *ulpi;
228c2ecf20Sopenharmony_ci	struct phy *phy;
238c2ecf20Sopenharmony_ci	struct gpio_desc *gpio_reset;
248c2ecf20Sopenharmony_ci	struct gpio_desc *gpio_cs;
258c2ecf20Sopenharmony_ci	u8 vendor_specific2;
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int tusb1210_power_on(struct phy *phy)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	struct tusb1210 *tusb = phy_get_drvdata(phy);
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_reset, 1);
338c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_cs, 1);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	/* Restore the optional eye diagram optimization value */
368c2ecf20Sopenharmony_ci	if (tusb->vendor_specific2)
378c2ecf20Sopenharmony_ci		ulpi_write(tusb->ulpi, TUSB1210_VENDOR_SPECIFIC2,
388c2ecf20Sopenharmony_ci			   tusb->vendor_specific2);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	return 0;
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic int tusb1210_power_off(struct phy *phy)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct tusb1210 *tusb = phy_get_drvdata(phy);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_reset, 0);
488c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_cs, 0);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	return 0;
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int tusb1210_set_mode(struct phy *phy, enum phy_mode mode, int submode)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct tusb1210 *tusb = phy_get_drvdata(phy);
568c2ecf20Sopenharmony_ci	int ret;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	ret = ulpi_read(tusb->ulpi, ULPI_OTG_CTRL);
598c2ecf20Sopenharmony_ci	if (ret < 0)
608c2ecf20Sopenharmony_ci		return ret;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	switch (mode) {
638c2ecf20Sopenharmony_ci	case PHY_MODE_USB_HOST:
648c2ecf20Sopenharmony_ci		ret |= (ULPI_OTG_CTRL_DRVVBUS_EXT
658c2ecf20Sopenharmony_ci			| ULPI_OTG_CTRL_ID_PULLUP
668c2ecf20Sopenharmony_ci			| ULPI_OTG_CTRL_DP_PULLDOWN
678c2ecf20Sopenharmony_ci			| ULPI_OTG_CTRL_DM_PULLDOWN);
688c2ecf20Sopenharmony_ci		ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
698c2ecf20Sopenharmony_ci		ret |= ULPI_OTG_CTRL_DRVVBUS;
708c2ecf20Sopenharmony_ci		break;
718c2ecf20Sopenharmony_ci	case PHY_MODE_USB_DEVICE:
728c2ecf20Sopenharmony_ci		ret &= ~(ULPI_OTG_CTRL_DRVVBUS
738c2ecf20Sopenharmony_ci			 | ULPI_OTG_CTRL_DP_PULLDOWN
748c2ecf20Sopenharmony_ci			 | ULPI_OTG_CTRL_DM_PULLDOWN);
758c2ecf20Sopenharmony_ci		ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
768c2ecf20Sopenharmony_ci		ret &= ~ULPI_OTG_CTRL_DRVVBUS_EXT;
778c2ecf20Sopenharmony_ci		break;
788c2ecf20Sopenharmony_ci	default:
798c2ecf20Sopenharmony_ci		/* nothing */
808c2ecf20Sopenharmony_ci		return 0;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	return ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret);
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic const struct phy_ops phy_ops = {
878c2ecf20Sopenharmony_ci	.power_on = tusb1210_power_on,
888c2ecf20Sopenharmony_ci	.power_off = tusb1210_power_off,
898c2ecf20Sopenharmony_ci	.set_mode = tusb1210_set_mode,
908c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
918c2ecf20Sopenharmony_ci};
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int tusb1210_probe(struct ulpi *ulpi)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct tusb1210 *tusb;
968c2ecf20Sopenharmony_ci	u8 val, reg;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL);
998c2ecf20Sopenharmony_ci	if (!tusb)
1008c2ecf20Sopenharmony_ci		return -ENOMEM;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset",
1038c2ecf20Sopenharmony_ci						   GPIOD_OUT_LOW);
1048c2ecf20Sopenharmony_ci	if (IS_ERR(tusb->gpio_reset))
1058c2ecf20Sopenharmony_ci		return PTR_ERR(tusb->gpio_reset);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_reset, 1);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs",
1108c2ecf20Sopenharmony_ci						GPIOD_OUT_LOW);
1118c2ecf20Sopenharmony_ci	if (IS_ERR(tusb->gpio_cs))
1128c2ecf20Sopenharmony_ci		return PTR_ERR(tusb->gpio_cs);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(tusb->gpio_cs, 1);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/*
1178c2ecf20Sopenharmony_ci	 * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye
1188c2ecf20Sopenharmony_ci	 * diagram optimization and DP/DM swap.
1198c2ecf20Sopenharmony_ci	 */
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	/* High speed output drive strength configuration */
1228c2ecf20Sopenharmony_ci	device_property_read_u8(&ulpi->dev, "ihstx", &val);
1238c2ecf20Sopenharmony_ci	reg = val << TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* High speed output impedance configuration */
1268c2ecf20Sopenharmony_ci	device_property_read_u8(&ulpi->dev, "zhsdrv", &val);
1278c2ecf20Sopenharmony_ci	reg |= val << TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	/* DP/DM swap control */
1308c2ecf20Sopenharmony_ci	device_property_read_u8(&ulpi->dev, "datapolarity", &val);
1318c2ecf20Sopenharmony_ci	reg |= val << TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	if (reg) {
1348c2ecf20Sopenharmony_ci		ulpi_write(ulpi, TUSB1210_VENDOR_SPECIFIC2, reg);
1358c2ecf20Sopenharmony_ci		tusb->vendor_specific2 = reg;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	tusb->phy = ulpi_phy_create(ulpi, &phy_ops);
1398c2ecf20Sopenharmony_ci	if (IS_ERR(tusb->phy))
1408c2ecf20Sopenharmony_ci		return PTR_ERR(tusb->phy);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	tusb->ulpi = ulpi;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	phy_set_drvdata(tusb->phy, tusb);
1458c2ecf20Sopenharmony_ci	ulpi_set_drvdata(ulpi, tusb);
1468c2ecf20Sopenharmony_ci	return 0;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void tusb1210_remove(struct ulpi *ulpi)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	struct tusb1210 *tusb = ulpi_get_drvdata(ulpi);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	ulpi_phy_destroy(ulpi, tusb->phy);
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci#define TI_VENDOR_ID 0x0451
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic const struct ulpi_device_id tusb1210_ulpi_id[] = {
1598c2ecf20Sopenharmony_ci	{ TI_VENDOR_ID, 0x1507, },  /* TUSB1210 */
1608c2ecf20Sopenharmony_ci	{ TI_VENDOR_ID, 0x1508, },  /* TUSB1211 */
1618c2ecf20Sopenharmony_ci	{ },
1628c2ecf20Sopenharmony_ci};
1638c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic struct ulpi_driver tusb1210_driver = {
1668c2ecf20Sopenharmony_ci	.id_table = tusb1210_ulpi_id,
1678c2ecf20Sopenharmony_ci	.probe = tusb1210_probe,
1688c2ecf20Sopenharmony_ci	.remove = tusb1210_remove,
1698c2ecf20Sopenharmony_ci	.driver = {
1708c2ecf20Sopenharmony_ci		.name = "tusb1210",
1718c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
1728c2ecf20Sopenharmony_ci	},
1738c2ecf20Sopenharmony_ci};
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cimodule_ulpi_driver(tusb1210_driver);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ciMODULE_AUTHOR("Intel Corporation");
1788c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1798c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TUSB1210 ULPI PHY driver");
180