Devicetree
 help / color / mirror / Atom feed
* [PATCH v6 12/15] usb: typec: tcpm: set cc for drp toggling attach
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

In case of drp toggling, we may need set correct cc value for role control
after attach as it may never been set.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/usb/typec/tcpm.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index d885bff..98ea916 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -2599,6 +2599,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
+	port->cc_req = TYPEC_CC_OPEN;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
 	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
@@ -2831,6 +2832,8 @@ static void run_state_machine(struct tcpm_port *port)
 		break;
 
 	case SRC_ATTACHED:
+		if (port->cc_req == TYPEC_CC_OPEN)
+			tcpm_set_cc(port, tcpm_rp_cc(port));
 		ret = tcpm_src_attach(port);
 		tcpm_set_state(port, SRC_UNATTACHED,
 			       ret < 0 ? 0 : PD_T_PS_SOURCE_ON);
@@ -3004,6 +3007,8 @@ static void run_state_machine(struct tcpm_port *port)
 		tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE);
 		break;
 	case SNK_ATTACHED:
+		if (port->cc_req == TYPEC_CC_OPEN)
+			tcpm_set_cc(port, TYPEC_CC_RD);
 		ret = tcpm_snk_attach(port);
 		if (ret < 0)
 			tcpm_set_state(port, SNK_UNATTACHED, 0);
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 11/15] typec: tcpm: add starting value for drp toggling
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

As DRP port autonomously toggles the Rp/Rd need a start value to
begin with, so add one parameter for it in tcpm_start_drp_toggling.

Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/usb/typec/tcpm.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index aa17cd5..d885bff 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -2436,15 +2436,15 @@ static int tcpm_set_charge(struct tcpm_port *port, bool charge)
 	return 0;
 }
 
-static bool tcpm_start_drp_toggling(struct tcpm_port *port)
+static bool tcpm_start_drp_toggling(struct tcpm_port *port,
+				    enum typec_cc_status cc)
 {
 	int ret;
 
 	if (port->tcpc->start_drp_toggling &&
 	    port->port_type == TYPEC_PORT_DRP) {
 		tcpm_log_force(port, "Start DRP toggling");
-		ret = port->tcpc->start_drp_toggling(port->tcpc,
-						     tcpm_rp_cc(port));
+		ret = port->tcpc->start_drp_toggling(port->tcpc, cc);
 		if (!ret)
 			return true;
 	}
@@ -2752,7 +2752,7 @@ static void run_state_machine(struct tcpm_port *port)
 		if (!port->non_pd_role_swap)
 			tcpm_swap_complete(port, -ENOTCONN);
 		tcpm_src_detach(port);
-		if (tcpm_start_drp_toggling(port)) {
+		if (tcpm_start_drp_toggling(port, tcpm_rp_cc(port))) {
 			tcpm_set_state(port, DRP_TOGGLING, 0);
 			break;
 		}
@@ -2927,7 +2927,7 @@ static void run_state_machine(struct tcpm_port *port)
 			tcpm_swap_complete(port, -ENOTCONN);
 		tcpm_pps_complete(port, -ENOTCONN);
 		tcpm_snk_detach(port);
-		if (tcpm_start_drp_toggling(port)) {
+		if (tcpm_start_drp_toggling(port, TYPEC_CC_RD)) {
 			tcpm_set_state(port, DRP_TOGGLING, 0);
 			break;
 		}
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 10/15] staging: typec: tcpci: enable vbus detection
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

TCPCI implementation may need SW to enable VBUS detection to generate
power status events.

Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index 3b35fce..4d3b0ae 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -373,6 +373,12 @@ static int tcpci_init(struct tcpc_dev *tcpc)
 	if (ret < 0)
 		return ret;
 
+	/* Enable Vbus detection */
+	ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+			   TCPC_CMD_ENABLE_VBUS_DETECT);
+	if (ret < 0)
+		return ret;
+
 	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
 		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
 		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 09/15] staging: typec: tcpci: register port before request irq
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

From: Peter Chen <peter.chen@nxp.com>

With that we can clear any pending events and the port is registered
so driver can be ready to handle typec events once we request irq.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index b63f147..3b35fce 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -537,24 +537,27 @@ static int tcpci_probe(struct i2c_client *client,
 	if (IS_ERR(chip->data.regmap))
 		return PTR_ERR(chip->data.regmap);
 
+	i2c_set_clientdata(client, chip);
+
 	/* Disable chip interrupts before requesting irq */
 	err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val,
 			       sizeof(u16));
 	if (err < 0)
 		return err;
 
+	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
+	if (IS_ERR(chip->tcpci))
+		return PTR_ERR(chip->tcpci);
+
 	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
 					_tcpci_irq,
 					IRQF_ONESHOT | IRQF_TRIGGER_LOW,
 					dev_name(&client->dev), chip);
-	if (err < 0)
+	if (err < 0) {
+		tcpci_unregister_port(chip->tcpci);
 		return err;
+	}
 
-	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
-	if (IS_ERR(chip->tcpci))
-		return PTR_ERR(chip->tcpci);
-
-	i2c_set_clientdata(client, chip);
 	return 0;
 }
 
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 08/15] staging: typec: tcpci: use IS_ERR() instead of PTR_ERR_OR_ZERO()
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

As tcpm_register_port() and tcpci_register_port() never return
NULL and NULL is not a success in this case, use IS_ERR() to check
the return value of both.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index 076498a..b63f147 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -509,7 +509,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
 		return ERR_PTR(err);
 
 	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
-	if (PTR_ERR_OR_ZERO(tcpci->port))
+	if (IS_ERR(tcpci->port))
 		return ERR_CAST(tcpci->port);
 
 	return tcpci;
@@ -551,7 +551,7 @@ static int tcpci_probe(struct i2c_client *client,
 		return err;
 
 	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
-	if (PTR_ERR_OR_ZERO(chip->tcpci))
+	if (IS_ERR(chip->tcpci))
 		return PTR_ERR(chip->tcpci);
 
 	i2c_set_clientdata(client, chip);
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 06/15] usb: typec: tcpm: support get typec and pd config from device properties
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

This patch adds support of get typec and power delivery config from
firmware description.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/usb/typec/tcpm.c | 132 +++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 110 insertions(+), 22 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index fcd22e8..aa17cd5 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -4241,6 +4241,81 @@ static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo,
 	return nr_vdo;
 }
 
+static int tcpm_fw_get_caps(struct tcpm_port *port,
+			    struct fwnode_handle *fwnode)
+{
+	const char *cap_str;
+	int ret;
+	u32 mw;
+
+	if (!port || !fwnode)
+		return -EINVAL;
+
+	/* USB data support is optional */
+	ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
+	if (ret == 0) {
+		port->typec_caps.data = typec_find_port_data_role(cap_str);
+		if (port->typec_caps.data < 0)
+			return -EINVAL;
+	}
+
+	ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+	if (ret < 0)
+		return ret;
+
+	port->typec_caps.type = typec_find_port_power_role(cap_str);
+	if (port->typec_caps.type < 0)
+		return -EINVAL;
+	port->port_type = port->typec_caps.type;
+
+	if (port->port_type == TYPEC_PORT_SNK)
+		goto sink;
+
+	/* Get soruce pdos */
+	ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
+					     NULL, 0);
+	if (ret <= 0)
+		return -EINVAL;
+
+	port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
+	ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
+					     port->src_pdo, port->nr_src_pdo);
+	if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo,
+					    port->nr_src_pdo))
+		return -EINVAL;
+
+	if (port->port_type == TYPEC_PORT_SRC)
+		return 0;
+
+	/* Get the preferred power role for DRP */
+	ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
+	if (ret < 0)
+		return ret;
+
+	port->typec_caps.prefer_role = typec_find_power_role(cap_str);
+	if (port->typec_caps.prefer_role < 0)
+		return -EINVAL;
+sink:
+	/* Get sink pdos */
+	ret = fwnode_property_read_u32_array(fwnode, "sink-pdos",
+					     NULL, 0);
+	if (ret <= 0)
+		return -EINVAL;
+
+	port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
+	ret = fwnode_property_read_u32_array(fwnode, "sink-pdos",
+					     port->snk_pdo, port->nr_snk_pdo);
+	if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo,
+					    port->nr_snk_pdo))
+		return -EINVAL;
+
+	if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0)
+		return -EINVAL;
+	port->operating_snk_mw = mw / 1000;
+
+	return 0;
+}
+
 int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
 				    unsigned int nr_pdo)
 {
@@ -4526,12 +4601,36 @@ static int devm_tcpm_psy_register(struct tcpm_port *port)
 	return PTR_ERR_OR_ZERO(port->psy);
 }
 
+static int tcpm_copy_caps(struct tcpm_port *port,
+			  const struct tcpc_config *tcfg)
+{
+	if (tcpm_validate_caps(port, tcfg->src_pdo, tcfg->nr_src_pdo) ||
+	    tcpm_validate_caps(port, tcfg->snk_pdo, tcfg->nr_snk_pdo))
+		return -EINVAL;
+
+	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcfg->src_pdo,
+					  tcfg->nr_src_pdo);
+	port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcfg->snk_pdo,
+					  tcfg->nr_snk_pdo);
+
+	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcfg->snk_vdo,
+					  tcfg->nr_snk_vdo);
+
+	port->operating_snk_mw = tcfg->operating_snk_mw;
+
+	port->typec_caps.prefer_role = tcfg->default_role;
+	port->typec_caps.type = tcfg->type;
+	port->typec_caps.data = tcfg->data;
+
+	return 0;
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
 	int i, err;
 
-	if (!dev || !tcpc || !tcpc->config ||
+	if (!dev || !tcpc ||
 	    !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc ||
 	    !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus ||
 	    !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit)
@@ -4561,30 +4660,19 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	init_completion(&port->pps_complete);
 	tcpm_debugfs_init(port);
 
-	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
-			       tcpc->config->nr_src_pdo) ||
-	    tcpm_validate_caps(port, tcpc->config->snk_pdo,
-			       tcpc->config->nr_snk_pdo)) {
-		err = -EINVAL;
+	if (tcpc->config)
+		err = tcpm_copy_caps(port, tcpc->config);
+	else
+		err = tcpm_fw_get_caps(port, tcpc->fwnode);
+	if (err < 0)
 		goto out_destroy_wq;
-	}
-	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
-					  tcpc->config->nr_src_pdo);
-	port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo,
-					  tcpc->config->nr_snk_pdo);
-	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
-					  tcpc->config->nr_snk_vdo);
-
-	port->operating_snk_mw = tcpc->config->operating_snk_mw;
-	if (!tcpc->config->try_role_hw)
-		port->try_role = tcpc->config->default_role;
+
+	if (!tcpc->config || !tcpc->config->try_role_hw)
+		port->try_role = port->typec_caps.prefer_role;
 	else
 		port->try_role = TYPEC_NO_PREFERRED_ROLE;
 
 	port->typec_caps.fwnode = tcpc->fwnode;
-	port->typec_caps.prefer_role = tcpc->config->default_role;
-	port->typec_caps.type = tcpc->config->type;
-	port->typec_caps.data = tcpc->config->data;
 	port->typec_caps.revision = 0x0120;	/* Type-C spec release 1.2 */
 	port->typec_caps.pd_revision = 0x0300;	/* USB-PD spec release 3.0 */
 	port->typec_caps.dr_set = tcpm_dr_set;
@@ -4594,7 +4682,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->typec_caps.port_type_set = tcpm_port_type_set;
 
 	port->partner_desc.identity = &port->partner_ident;
-	port->port_type = tcpc->config->type;
+	port->port_type = port->typec_caps.type;
 
 	port->role_sw = usb_role_switch_get(port->dev);
 	if (IS_ERR(port->role_sw)) {
@@ -4612,7 +4700,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 		goto out_destroy_wq;
 	}
 
-	if (tcpc->config->alt_modes) {
+	if (tcpc->config && tcpc->config->alt_modes) {
 		const struct typec_altmode_desc *paltmode = tcpc->config->alt_modes;
 
 		i = 0;
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 05/15] usb: typec: add API to get typec basic port power and data config
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

This patch adds 3 APIs to get the typec port power and data type,
and preferred power role by its name string.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/usb/typec/class.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/typec.h |  3 +++
 2 files changed, 53 insertions(+)

diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 53df10d..4c7d18c 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -802,6 +802,12 @@ static const char * const typec_port_types[] = {
 	[TYPEC_PORT_DRP] = "dual",
 };
 
+static const char * const typec_data_caps[] = {
+	[TYPEC_PORT_DFP] = "host",
+	[TYPEC_PORT_UFP] = "device",
+	[TYPEC_PORT_DRD] = "dual",
+};
+
 static const char * const typec_port_types_drp[] = {
 	[TYPEC_PORT_SRC] = "dual [source] sink",
 	[TYPEC_PORT_SNK] = "dual source [sink]",
@@ -1252,6 +1258,50 @@ void typec_set_pwr_opmode(struct typec_port *port,
 }
 EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
 
+/**
+ * typec_find_port_power_role - Get the typec port power capability
+ * @name: port power capability string
+ *
+ * This routine is used to find the typec_port_type by its string name.
+ *
+ * Returns typec_port_type if success, otherwise negative error code.
+ */
+int typec_find_port_power_role(const char *name)
+{
+	return match_string(typec_port_types, ARRAY_SIZE(typec_port_types),
+			    name);
+}
+EXPORT_SYMBOL_GPL(typec_find_power_type);
+
+/**
+ * typec_find_power_role - Find the typec one specific power role
+ * @name: power role string
+ *
+ * This routine is used to find the typec_role by its string name.
+ *
+ * Returns typec_role if success, otherwise negative error code.
+ */
+int typec_find_power_role(const char *name)
+{
+	return match_string(typec_roles, ARRAY_SIZE(typec_roles), name);
+}
+EXPORT_SYMBOL_GPL(typec_find_preferred_role);
+
+/**
+ * typec_find_port_data_role - Get the typec port data capability
+ * @name: port data capability string
+ *
+ * This routine is used to find the typec_port_data by its string name.
+ *
+ * Returns typec_port_data if success, otherwise negative error code.
+ */
+int typec_find_port_data_role(const char *name)
+{
+	return match_string(typec_data_caps, ARRAY_SIZE(typec_data_caps),
+			    name);
+}
+EXPORT_SYMBOL_GPL(typec_find_data_type);
+
 /* ------------------------------------------ */
 /* API for Multiplexer/DeMultiplexer Switches */
 
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 672b39b..15f8d9a 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -267,4 +267,7 @@ int typec_set_orientation(struct typec_port *port,
 			  enum typec_orientation orientation);
 int typec_set_mode(struct typec_port *port, int mode);
 
+int typec_find_port_power_role(const char *name);
+int typec_find_power_role(const char *name);
+int typec_find_port_data_role(const char *name);
 #endif /* __LINUX_USB_TYPEC_H */
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 04/15] usb: typec: add fwnode to tcpc
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

Add fwnode handle to get the fwnode so we can get typec configs
it contains.

Suggested-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 7 +++++++
 drivers/usb/typec/tcpm.c      | 1 +
 include/linux/usb/tcpm.h      | 2 ++
 3 files changed, 10 insertions(+)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index dd29288..e59547a 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -10,6 +10,7 @@
 #include <linux/module.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/usb/pd.h>
 #include <linux/usb/tcpm.h>
@@ -474,6 +475,12 @@ static int tcpci_parse_config(struct tcpci *tcpci)
 
 	/* TODO: Populate struct tcpc_config from ACPI/device-tree */
 	tcpci->tcpc.config = &tcpci_tcpc_config;
+	tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev,
+							 "connector");
+	if (!tcpci->tcpc.fwnode) {
+		dev_err(tcpci->dev, "Can't find connector node.\n");
+		return -EINVAL;
+	}
 
 	return 0;
 }
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 0ccd2ce..fcd22e8 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -4581,6 +4581,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	else
 		port->try_role = TYPEC_NO_PREFERRED_ROLE;
 
+	port->typec_caps.fwnode = tcpc->fwnode;
 	port->typec_caps.prefer_role = tcpc->config->default_role;
 	port->typec_caps.type = tcpc->config->type;
 	port->typec_caps.data = tcpc->config->data;
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index b231b93..193920a 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -110,6 +110,7 @@ enum tcpc_mux_mode {
 /**
  * struct tcpc_dev - Port configuration and callback functions
  * @config:	Pointer to port configuration
+ * @fwnode:	Pointer to port fwnode
  * @get_vbus:	Called to read current VBUS state
  * @get_current_limit:
  *		Optional; called by the tcpm core when configured as a snk
@@ -138,6 +139,7 @@ enum tcpc_mux_mode {
  */
 struct tcpc_dev {
 	const struct tcpc_config *config;
+	struct fwnode_handle *fwnode;
 
 	int (*init)(struct tcpc_dev *dev);
 	int (*get_vbus)(struct tcpc_dev *dev);
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 03/15] staging: typec: tcpci: add compatible string for nxp ptn5110
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

Add nxp ptn5110 typec controller compatible string: "nxp,ptn5110",
which is a standard tcpci chip with power delivery support. Meanwhile
remove "usb,tcpci" because it doesn't follow the binding format rule
and has not been used yet.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index 076d97e..dd29288 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -575,7 +575,7 @@ MODULE_DEVICE_TABLE(i2c, tcpci_id);
 
 #ifdef CONFIG_OF
 static const struct of_device_id tcpci_of_match[] = {
-	{ .compatible = "usb,tcpci", },
+	{ .compatible = "nxp,ptn5110", },
 	{},
 };
 MODULE_DEVICE_TABLE(of, tcpci_of_match);
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 02/15] dt-bindings: usb: add documentation for typec port controller(TCPCI)
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

TCPCI stands for typec port controller interface, its implementation
has full typec port control with power delivery support, it's a
standard i2c slave with GPIO input as irq interface, detail see spec
"Universal Serial Bus Type-C Port Controller Interface Specification
Revision 1.0, Version 1.1"

Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Li Jun <jun.li@nxp.com>
---
 .../devicetree/bindings/usb/typec-tcpci.txt        | 49 ++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/Documentation/devicetree/bindings/usb/typec-tcpci.txt b/Documentation/devicetree/bindings/usb/typec-tcpci.txt
new file mode 100644
index 0000000..0dd1469
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/typec-tcpci.txt
@@ -0,0 +1,49 @@
+TCPCI(Typec port cotroller interface) binding
+---------------------------------------------
+
+Required properties:
+- compatible:       should be set one of following:
+		    - "nxp,ptn5110" for NXP USB PD TCPC PHY IC ptn5110.
+
+- reg:              the i2c slave address of typec port controller device.
+- interrupt-parent: the phandle to the interrupt controller which provides
+                    the interrupt.
+- interrupts:       interrupt specification for tcpci alert.
+
+Required sub-node:
+- connector: The "usb-c-connector" attached to the tcpci chip, the bindings
+  of connector node are specified in
+  Documentation/devicetree/bindings/connector/usb-connector.txt
+
+Example:
+
+ptn5110@50 {
+	compatible = "nxp,ptn5110";
+	reg = <0x50>;
+	interrupt-parent = <&gpio3>;
+	interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
+
+	usb_con: connector {
+		compatible = "usb-c-connector";
+		label = "USB-C";
+		data-role = "dual";
+		power-role = "dual";
+		try-power-role = "sink";
+		source-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)>;
+		sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)
+			     PDO_VAR(5000, 12000, 2000)>;
+		op-sink-microwatt = <10000000>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@1 {
+				reg = <1>;
+				usb_con_ss: endpoint {
+					remote-endpoint = <&usb3_data_ss>;
+				};
+			};
+		};
+	};
+};
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 01/15] dt-bindings: connector: add properties for typec
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

Add bindings supported by current typec driver, so user can pass
all those properties via dt.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 .../bindings/connector/usb-connector.txt           | 44 +++++++++++++++
 include/dt-bindings/usb/pd.h                       | 62 ++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/Documentation/devicetree/bindings/connector/usb-connector.txt b/Documentation/devicetree/bindings/connector/usb-connector.txt
index e1463f1..8855bfc 100644
--- a/Documentation/devicetree/bindings/connector/usb-connector.txt
+++ b/Documentation/devicetree/bindings/connector/usb-connector.txt
@@ -15,6 +15,33 @@ Optional properties:
 - type: size of the connector, should be specified in case of USB-A, USB-B
   non-fullsize connectors: "mini", "micro".
 
+Optional properties for usb-c-connector:
+- power-role: should be one of "source", "sink" or "dual"(DRP) if typec
+  connector has power support.
+- try-power-role: preferred power role if "dual"(DRP) can support Try.SNK
+  or Try.SRC, should be "sink" for Try.SNK or "source" for Try.SRC.
+- data-role: should be one of "host", "device", "dual"(DRD) if typec
+  connector supports USB data.
+
+Required properties for usb-c-connector with power delivery support:
+- source-pdos: An array of u32 with each entry providing supported power
+  source data object(PDO), the detailed bit definitions of PDO can be found
+  in "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.2
+  Source_Capabilities Message, the order of each entry(PDO) should follow
+  the PD spec chapter 6.4.1. Required for power source and power dual role.
+  User can specify the source PDO array via PDO_FIXED/BATT/VAR() defined in
+  dt-bindings/usb/pd.h.
+- sink-pdos: An array of u32 with each entry providing supported power
+  sink data object(PDO), the detailed bit definitions of PDO can be found
+  in "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.3
+  Sink Capabilities Message, the order of each entry(PDO) should follow
+  the PD spec chapter 6.4.1. Required for power sink and power dual role.
+  User can specify the sink PDO array via PDO_FIXED/BATT/VAR() defined in
+  dt-bindings/usb/pd.h.
+- op-sink-microwatt: Sink required operating power in microwatt, if source
+  can't offer the power, Capability Mismatch is set. Required for power
+  sink and power dual role.
+
 Required nodes:
 - any data bus to the connector should be modeled using the OF graph bindings
   specified in bindings/graph.txt, unless the bus is between parent node and
@@ -73,3 +100,20 @@ ccic: s2mm005@33 {
 		};
 	};
 };
+
+3. USB-C connector attached to a typec port controller(ptn5110), which has
+power delivery support and enables drp.
+
+typec: ptn5110@50 {
+	...
+	usb_con: connector {
+		compatible = "usb-c-connector";
+		label = "USB-C";
+		power-role = "dual";
+		try-power-role = "sink";
+		source-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)>;
+		sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_USB_COMM)
+			     PDO_VAR(5000, 12000, 2000)>;
+		op-sink-microwatt = <10000000>;
+	};
+};
diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h
new file mode 100644
index 0000000..7b7a92f
--- /dev/null
+++ b/include/dt-bindings/usb/pd.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DT_POWER_DELIVERY_H
+#define __DT_POWER_DELIVERY_H
+
+/* Power delivery Power Data Object definitions */
+#define PDO_TYPE_FIXED		0
+#define PDO_TYPE_BATT		1
+#define PDO_TYPE_VAR		2
+#define PDO_TYPE_APDO		3
+
+#define PDO_TYPE_SHIFT		30
+#define PDO_TYPE_MASK		0x3
+
+#define PDO_TYPE(t)	((t) << PDO_TYPE_SHIFT)
+
+#define PDO_VOLT_MASK		0x3ff
+#define PDO_CURR_MASK		0x3ff
+#define PDO_PWR_MASK		0x3ff
+
+#define PDO_FIXED_DUAL_ROLE	(1 << 29) /* Power role swap supported */
+#define PDO_FIXED_SUSPEND	(1 << 28) /* USB Suspend supported (Source) */
+#define PDO_FIXED_HIGHER_CAP	(1 << 28) /* Requires more than vSafe5V (Sink) */
+#define PDO_FIXED_EXTPOWER	(1 << 27) /* Externally powered */
+#define PDO_FIXED_USB_COMM	(1 << 26) /* USB communications capable */
+#define PDO_FIXED_DATA_SWAP	(1 << 25) /* Data role swap supported */
+#define PDO_FIXED_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_FIXED_CURR_SHIFT	0	/* 10mA units */
+
+#define PDO_FIXED_VOLT(mv)	((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT)
+#define PDO_FIXED_CURR(ma)	((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT)
+
+#define PDO_FIXED(mv, ma, flags)			\
+	(PDO_TYPE(PDO_TYPE_FIXED) | (flags) |		\
+	 PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))
+
+#define VSAFE5V 5000 /* mv units */
+
+#define PDO_BATT_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_BATT_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_BATT_MAX_PWR_SHIFT	0	/* 250mW units */
+
+#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MIN_VOLT_SHIFT)
+#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MAX_VOLT_SHIFT)
+#define PDO_BATT_MAX_POWER(mw) ((((mw) / 250) & PDO_PWR_MASK) << PDO_BATT_MAX_PWR_SHIFT)
+
+#define PDO_BATT(min_mv, max_mv, max_mw)			\
+	(PDO_TYPE(PDO_TYPE_BATT) | PDO_BATT_MIN_VOLT(min_mv) |	\
+	 PDO_BATT_MAX_VOLT(max_mv) | PDO_BATT_MAX_POWER(max_mw))
+
+#define PDO_VAR_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_VAR_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_VAR_MAX_CURR_SHIFT	0	/* 10mA units */
+
+#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT)
+#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT)
+#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT)
+
+#define PDO_VAR(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) |	\
+	 PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))
+
+ #endif /* __DT_POWER_DELIVERY_H */
-- 
2.7.4


^ permalink raw reply related

* [PATCH v6 00/15] staging: typec: tcpci: move out of staging
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx

This patch set attempts to move the tcpci drivers out of staging by fix
some tcpci driver issues and define typec and power delivery device
properties, the changes are verified on NXP PTN5110, which is a standard
tcpci typec port controller device with power delivery support, tested
power source and sink with drp config.

Changes for v6:
- Change function name to be typec_find_port_power/data_role for find
  capability, and typec_find_power_role for find one specific power role.
- Fix rt1711h driver move missing.
- Add one patch to improve the error checking in tcpci driver, use IS_ERR()
  instead of PTR_ERR_OR_ZERO().
- Add Rob's Reviewed-by for patch [2/15].
- Misc typos fix.
 
Changes for v5:
- Use "power-role" and "data-role" for typec port power and data capability
  property name.  
- Use macro defintion instead of cryptic numbers for readability of
  power delivery properties(PDO).
- Add one tcpci driver binding string to be "nxp,ptn5110" to follow binding
  name rule "<vendor prefix>,<device>".
- Change operating power property name to be op-sink-microwatt.
- Add one patch(remove unused tcpci_tcpc_config) to resolve bisectablitity
  issue of patch usb: typec: add fwnode to tcpc.
- A minor error handling change to keep the error path and success path
  separate in patch: register port before request irq
- Rebase to latest Greg's tree with max_snk_* removed.

Changes for v4:
- Remove max-sink-* properties as we will purge max_snk_* in tcpm,
  see patch set[4].
- Get typec power and data type value via name string(patch 5).
- Move finding typec and pd properties code from tcpci to tcpm(patch 6)
- Add a compatible string for nxp ptn5110 typec controller in tcpci driver.
  (patch 3)
- Add set cc for drp toggling without try.src/snk in tcpm(patch 10), then
  patch 11 can only update CCx bits for keep disconnect cc line open.
- Update op-sink-microwatt-hours example value to be the right value in
  micorwatts, and accordingly divide 1000 to get its miliwatts value
  in patch 6.
- Add Guenter's Reviewed-by for patch(8/9/12)

[4] https://www.spinics.net/lists/linux-usb/msg167261.html

Changes for v3:
- Use 2 properties to separate power and data capability of typec port:
  "power-type" and "data-type", this is based on Heikki's typec class code
  change[2]. use "try-power-role" to present if the typec port can support
  Try.SNK or Try.SRC.
- 4 sink properties(max_sink_mv/ma/mw and op_sink_mw) are kept because the
  counterpart code is back, see revert patch[3], meanwhile I post a patch
  to fix the reported problem of current source pdo select machinism(which
  completely ignored those 4 sink settings), to see if we can keep current
  code, once it was discussed and have conclusion I can update this
  accordingly.
- Use fwnode to get the connector node for dt setting parse.

Main changes for v2:
- Typec properties are based on general usb connector bindings[1] proposed
  by Andrzej Hajda, use the standard unit suffixes as defined in
  property-units.txt.
- Add 2 infra APIs to get power sink and source config from dt.
- Don't change the set_cc api, to keep the uncontacted cc line open,
  set cc1/cc2 to be open in tcpci driver when set polarity.
- Directly enable vbus detect in tcpci driver rather than add a API.
- Details added in each patch.

[1] https://patchwork.kernel.org/patch/10231447/
[2] https://patchwork.kernel.org/patch/10276483/
[3] https://www.spinics.net/lists/linux-usb/msg166366.html

Li Jun (14):
  dt-bindings: connector: add properties for typec
  dt-bindings: usb: add documentation for typec port controller(TCPCI)
  staging: typec: tcpci: add compatible string for nxp ptn5110
  usb: typec: add fwnode to tcpc
  usb: typec: add API to get typec basic port power and data config
  usb: typec: tcpm: support get typec and pd config from device
    properties
  staging: typec: tcpci: remove unused tcpci_tcpc_config
  staging: typec: tcpci: use IS_ERR() instead of PTR_ERR_OR_ZERO()
  staging: typec: tcpci: enable vbus detection
  typec: tcpm: add starting value for drp toggling
  usb: typec: tcpm: set cc for drp toggling attach
  staging: typec: tcpci: keep the disconnected cc line open
  staging: typec: tcpci: Only touch target bit when enable vconn
  staging: typec: tcpci: move tcpci drivers out of staging

Peter Chen (1):
  staging: typec: tcpci: register port before request irq

 .../bindings/connector/usb-connector.txt           |  44 ++++++
 .../devicetree/bindings/usb/typec-tcpci.txt        |  49 +++++++
 drivers/staging/Kconfig                            |   2 -
 drivers/staging/Makefile                           |   1 -
 drivers/staging/typec/Kconfig                      |  22 ---
 drivers/staging/typec/Makefile                     |   2 -
 drivers/staging/typec/TODO                         |   5 -
 drivers/usb/typec/Kconfig                          |  15 +++
 drivers/usb/typec/Makefile                         |   2 +
 drivers/usb/typec/class.c                          |  50 +++++++
 drivers/{staging => usb}/typec/tcpci.c             |  66 +++++----
 drivers/{staging => usb}/typec/tcpci.h             |   0
 drivers/{staging => usb}/typec/tcpci_rt1711h.c     |   0
 drivers/usb/typec/tcpm.c                           | 148 +++++++++++++++++----
 include/dt-bindings/usb/pd.h                       |  62 +++++++++
 include/linux/usb/tcpm.h                           |   2 +
 include/linux/usb/typec.h                          |   3 +
 17 files changed, 389 insertions(+), 84 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/usb/typec-tcpci.txt
 delete mode 100644 drivers/staging/typec/Kconfig
 delete mode 100644 drivers/staging/typec/Makefile
 delete mode 100644 drivers/staging/typec/TODO
 rename drivers/{staging => usb}/typec/tcpci.c (92%)
 rename drivers/{staging => usb}/typec/tcpci.h (100%)
 rename drivers/{staging => usb}/typec/tcpci_rt1711h.c (100%)
 create mode 100644 include/dt-bindings/usb/pd.h

-- 
2.7.4


^ permalink raw reply

* [PATCH v6 07/15] staging: typec: tcpci: remove unused tcpci_tcpc_config
From: Li Jun @ 2018-05-28  2:52 UTC (permalink / raw)
  To: robh+dt, gregkh, heikki.krogerus, linux
  Cc: cw00.choi, a.hajda, shufan_lee, peter.chen, garsilva, gsomlo,
	jun.li, linux-usb, devicetree, linux-imx
In-Reply-To: <1527475967-15201-1-git-send-email-jun.li@nxp.com>

Since we will use config settings via device properties, so
remove the hard code tcpci_tcpc_config.

Signed-off-by: Li Jun <jun.li@nxp.com>
---
 drivers/staging/typec/tcpci.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/drivers/staging/typec/tcpci.c b/drivers/staging/typec/tcpci.c
index e59547a..076498a 100644
--- a/drivers/staging/typec/tcpci.c
+++ b/drivers/staging/typec/tcpci.c
@@ -464,17 +464,10 @@ static const struct regmap_config tcpci_regmap_config = {
 	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
 };
 
-static const struct tcpc_config tcpci_tcpc_config = {
-	.type = TYPEC_PORT_DFP,
-	.default_role = TYPEC_SINK,
-};
-
 static int tcpci_parse_config(struct tcpci *tcpci)
 {
 	tcpci->controls_vbus = true; /* XXX */
 
-	/* TODO: Populate struct tcpc_config from ACPI/device-tree */
-	tcpci->tcpc.config = &tcpci_tcpc_config;
 	tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev,
 							 "connector");
 	if (!tcpci->tcpc.fwnode) {
-- 
2.7.4

^ permalink raw reply related

* Re: [PATCH v3 8/8] drm/mediatek: add third ddp path
From: CK Hu @ 2018-05-28  2:50 UTC (permalink / raw)
  To: Stu Hsieh
  Cc: Mark Rutland, devicetree, srv_heupstream, David Airlie,
	linux-kernel, dri-devel, Rob Herring, linux-mediatek,
	Matthias Brugger, linux-arm-kernel
In-Reply-To: <1527474417.11190.21.camel@mtksdccf07>

Hi, Stu:

On Mon, 2018-05-28 at 10:26 +0800, Stu Hsieh wrote:
> Hi, CK:
> I've some idea as below.
> 
> On Fri, 2018-05-25 at 13:00 +0800, CK Hu wrote:
> > Hi, Stu:
> > 
> > On Fri, 2018-05-25 at 10:34 +0800, stu.hsieh@mediatek.com wrote:
> > > From: Stu Hsieh <stu.hsieh@mediatek.com>
> > > 
> > > This patch create third crtc by third ddp path
> > > 
> > 
> > Apply this patch before the patch 'Add support for mediatek SOC MT2712'
> > because this patch is necessary for mt2712.
> > 
> > > Signed-off-by: Stu Hsieh <stu.hsieh@mediatek.com>
> > > ---
> > >  drivers/gpu/drm/mediatek/mtk_drm_drv.c | 5 +++++
> > >  1 file changed, 5 insertions(+)
> > > 
> > > diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > > index b32c4cc8d051..3a866e1d6af4 100644
> > > --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > > +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > > @@ -267,6 +267,11 @@ static int mtk_drm_kms_init(struct drm_device *drm)
> > >  	if (ret < 0)
> > >  		goto err_component_unbind;
> > >  
> > > +	ret = mtk_drm_crtc_create(drm, private->data->third_path,
> > > +				  private->data->third_len);
> > 
> > I think you should prevent doing this for mt8173 and mt2701 because that
> > two SoC has only two ddp path.
> 
> Now, 8173 and 2701 have only two ddp path, there is a problem on run
> time. 
> There are three crtc for drm resource, and there is nothing in third
> crtc. 
> Because 8173 and 2701 have no third ddp, and the third ddp_len is zero.
> So, I want to add the condition like following in mtk_crtc_create()
> if (path_len == 0)
> 	return 0;
> 
> Then, the valur ret is zero and it would not create the null third crtc.

This sounds good to me.

Regards,
CK

> 
> 
> Regards,
> Stu
> 
> > 
> > Regards,
> > CK
> > 
> > > +	if (ret < 0)
> > > +		goto err_component_unbind;
> > > +
> > >  	/* Use OVL device for all DMA memory allocations */
> > >  	np = private->comp_node[private->data->main_path[0]] ?:
> > >  	     private->comp_node[private->data->ext_path[0]];
> > 
> > 
> 
> 


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply

* Re: [PATCH v3 8/8] drm/mediatek: add third ddp path
From: Stu Hsieh @ 2018-05-28  2:26 UTC (permalink / raw)
  To: CK Hu
  Cc: Mark Rutland, devicetree, srv_heupstream, David Airlie,
	linux-kernel, dri-devel, Rob Herring, linux-mediatek,
	Matthias Brugger, linux-arm-kernel
In-Reply-To: <1527224425.27165.27.camel@mtksdaap41>

Hi, CK:
I've some idea as below.

On Fri, 2018-05-25 at 13:00 +0800, CK Hu wrote:
> Hi, Stu:
> 
> On Fri, 2018-05-25 at 10:34 +0800, stu.hsieh@mediatek.com wrote:
> > From: Stu Hsieh <stu.hsieh@mediatek.com>
> > 
> > This patch create third crtc by third ddp path
> > 
> 
> Apply this patch before the patch 'Add support for mediatek SOC MT2712'
> because this patch is necessary for mt2712.
> 
> > Signed-off-by: Stu Hsieh <stu.hsieh@mediatek.com>
> > ---
> >  drivers/gpu/drm/mediatek/mtk_drm_drv.c | 5 +++++
> >  1 file changed, 5 insertions(+)
> > 
> > diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > index b32c4cc8d051..3a866e1d6af4 100644
> > --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c
> > @@ -267,6 +267,11 @@ static int mtk_drm_kms_init(struct drm_device *drm)
> >  	if (ret < 0)
> >  		goto err_component_unbind;
> >  
> > +	ret = mtk_drm_crtc_create(drm, private->data->third_path,
> > +				  private->data->third_len);
> 
> I think you should prevent doing this for mt8173 and mt2701 because that
> two SoC has only two ddp path.

Now, 8173 and 2701 have only two ddp path, there is a problem on run
time. 
There are three crtc for drm resource, and there is nothing in third
crtc. 
Because 8173 and 2701 have no third ddp, and the third ddp_len is zero.
So, I want to add the condition like following in mtk_crtc_create()
if (path_len == 0)
	return 0;

Then, the valur ret is zero and it would not create the null third crtc.


Regards,
Stu

> 
> Regards,
> CK
> 
> > +	if (ret < 0)
> > +		goto err_component_unbind;
> > +
> >  	/* Use OVL device for all DMA memory allocations */
> >  	np = private->comp_node[private->data->main_path[0]] ?:
> >  	     private->comp_node[private->data->ext_path[0]];
> 
> 


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply

* Re: [PATCH 1/3] ARM: dts: imx6 wandboard and riotboard: remove regulators bus node
From: Fabio Estevam @ 2018-05-28  0:56 UTC (permalink / raw)
  To: Alexander Kurz
  Cc: Fabio Estevam,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	Shawn Guo,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
In-Reply-To: <20180526183053.14029-2-akurz@blala.de>

On Sat, May 26, 2018 at 3:30 PM, Alexander Kurz <akurz@blala.de> wrote:

> +       reg_usb_otg_vbus: fixedregulator@2 {
> +               compatible = "regulator-fixed";
> +               regulator-name = "usb_otg_vbus";
> +               regulator-min-microvolt = <5000000>;
> +               regulator-max-microvolt = <5000000>;
> +               gpio = <&gpio3 22 0>;

Please use  gpio = <&gpio3 22 GPIO_ACTIVE_HIGH>; instead.

> +               enable-active-high;

^ permalink raw reply

* Re: [PATCH v2 2/6] mtd: rawnand: tegra: add devicetree binding
From: Miquel Raynal @ 2018-05-27 22:23 UTC (permalink / raw)
  To: Stefan Agner
  Cc: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd, dev, richard,
	marcel, krzk, digetx, benjamin.lindqvist, jonathanh, pdeschrijver,
	pgaikwad, mirza.krak, linux-mtd, linux-tegra, devicetree,
	linux-kernel, linux-clk
In-Reply-To: <20180527215442.14760-3-stefan@agner.ch>

Hi Stefan,

On Sun, 27 May 2018 23:54:38 +0200, Stefan Agner <stefan@agner.ch>
wrote:

> From: Lucas Stach <dev@lynxeye.de>
> 
> This adds the devicetree binding for the Tegra 2 NAND flash
> controller.
> 
> Signed-off-by: Lucas Stach <dev@lynxeye.de>
> Signed-off-by: Stefan Agner <stefan@agner.ch>
> ---
>  .../bindings/mtd/nvidia,tegra20-nand.txt      | 62 +++++++++++++++++++
>  1 file changed, 62 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
> 
> diff --git a/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
> new file mode 100644
> index 000000000000..49e472af1b39
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
> @@ -0,0 +1,62 @@
> +NVIDIA Tegra NAND Flash controller
> +
> +Required properties:
> +- compatible: Must be one of:

Nitpick: just put the compatible here as there is only one?

> +  - "nvidia,tegra20-nand"
> +- reg: MMIO address range
> +- interrupts: interrupt output of the NFC controller
> +- clocks: Must contain an entry for each entry in clock-names.
> +  See ../clocks/clock-bindings.txt for details.
> +- clock-names: Must include the following entries:
> +  - nand
> +- resets: Must contain an entry for each entry in reset-names.
> +  See ../reset/reset.txt for details.
> +- reset-names: Must include the following entries:
> +  - nand
> +
> +Optional children nodes:
> +Individual NAND chips are children of the NAND controller node. Currently
> +only one NAND chip supported.
> +
> +Required children node properties:
> +- reg: An integer ranging from 1 to 6 representing the CS line to use.

>From 1? It usually starts at 0.

> +
> +Optional children node properties:
> +- nand-ecc-mode: String, operation mode of the NAND ecc mode. "hw" by default
> +- nand-ecc-algo: string, algorithm of NAND ECC.
> +		 Supported values are: "rs", "bch".
> +- nand-bus-width : 8 or 16 bus width if not present 8
> +- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
> +- nand-ecc-strength: integer representing the number of bits to correct
> +		     per ECC step. Supported strength using HW ECC modes are:
> +		     - RS: 4, 6, 8
> +		     - BCH: 4, 8, 14, 16
> +- nand-ecc-step-size: integer representing the number of data bytes
> +		      that are covered by a single ECC step. Must be 512.

Please don't re-explain nand generic DT properties, point to nand.txt
instead.

> +- wp-gpios: GPIO specifier for the write protect pin.
> +
> +Optional child node of NAND chip nodes:
> +Partitions: see partition.txt
> +
> +  Example:
> +	nand@70008000 {
> +		compatible = "nvidia,tegra20-nand";
> +		reg = <0x70008000 0x100>;
> +		interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
> +		clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
> +		clock-names = "nand";
> +		resets = <&tegra_car 13>;
> +		reset-names = "nand";
> +
> +		nand-chip@0 {
> +			reg = <0>;
> +			#address-cells = <1>;
> +			#size-cells = <1>;
> +			nand-bus-width = <8>;
> +			nand-on-flash-bbt;
> +			nand-ecc-algo = "bch";
> +			nand-ecc-step-size = <512>;

In the driver you only refer to step sizes of 512 B, you can remove the
property from the bindings.

> +			nand-ecc-strength = <8>;
> +			wp-gpios = <&gpio TEGRA_GPIO(S, 0) GPIO_ACTIVE_LOW>;
> +		};
> +	};



-- 
Miquel Raynal, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply

* Re: [PATCH v2 3/6] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Miquel Raynal @ 2018-05-27 22:19 UTC (permalink / raw)
  To: Stefan Agner
  Cc: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd, dev, richard,
	marcel, krzk, digetx, benjamin.lindqvist, jonathanh, pdeschrijver,
	pgaikwad, mirza.krak, linux-mtd, linux-tegra, devicetree,
	linux-kernel, linux-clk
In-Reply-To: <20180527215442.14760-4-stefan@agner.ch>

Hi Stefan,

A few more comments here.

On Sun, 27 May 2018 23:54:39 +0200, Stefan Agner <stefan@agner.ch>
wrote:

> Add support for the NAND flash controller found on NVIDIA
> Tegra 2 SoCs. This implementation does not make use of the
> command queue feature. Regular operations/data transfers are
> done in PIO mode. Page read/writes with hardware ECC make
> use of the DMA for data transfer.
> 
> Signed-off-by: Lucas Stach <dev@lynxeye.de>
> Signed-off-by: Stefan Agner <stefan@agner.ch>
> ---
>  MAINTAINERS                       |   7 +
>  drivers/mtd/nand/raw/Kconfig      |   6 +
>  drivers/mtd/nand/raw/Makefile     |   1 +
>  drivers/mtd/nand/raw/tegra_nand.c | 999 ++++++++++++++++++++++++++++++
>  4 files changed, 1013 insertions(+)
>  create mode 100644 drivers/mtd/nand/raw/tegra_nand.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 58b9861ccf99..8cbbb7111742 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13844,6 +13844,13 @@ M:	Laxman Dewangan <ldewangan@nvidia.com>
>  S:	Supported
>  F:	drivers/input/keyboard/tegra-kbc.c
>  
> +TEGRA NAND DRIVER
> +M:	Stefan Agner <stefan@agner.ch>
> +M:	Lucas Stach <dev@lynxeye.de>
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt

I think most MTD bindings use '-' instead of ','. I don't have a
preference, it's just for coherence.

> +F:	drivers/mtd/nand/raw/tegra_nand.c
> +
>  TEGRA PWM DRIVER
>  M:	Thierry Reding <thierry.reding@gmail.com>
>  S:	Supported
> diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
> index 19a2b283fbbe..012c63c6ab47 100644
> --- a/drivers/mtd/nand/raw/Kconfig
> +++ b/drivers/mtd/nand/raw/Kconfig
> @@ -534,4 +534,10 @@ config MTD_NAND_MTK
>  	  Enables support for NAND controller on MTK SoCs.
>  	  This controller is found on mt27xx, mt81xx, mt65xx SoCs.
>  
> +config MTD_NAND_TEGRA
> +	tristate "Support for NAND on NVIDIA Tegra"
> +	depends on ARCH_TEGRA || COMPILE_TEST
> +	help
> +	  Enables support for NAND flash on NVIDIA Tegra SoC based boards.

Please make the term "controller" appear because it's mostly a
controller driver that you're adding.

> +
>  endif # MTD_NAND
> diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
> index 165b7ef9e9a1..d5a5f9832b88 100644
> --- a/drivers/mtd/nand/raw/Makefile
> +++ b/drivers/mtd/nand/raw/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504)	        += hisi504_nand.o
>  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand/
>  obj-$(CONFIG_MTD_NAND_QCOM)		+= qcom_nandc.o
>  obj-$(CONFIG_MTD_NAND_MTK)		+= mtk_ecc.o mtk_nand.o
> +obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
>  
>  nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
>  nand-objs += nand_amd.o

[...]

> +static int tegra_nand_setup_data_interface(struct mtd_info *mtd, int csline,
> +					   const struct nand_data_interface *conf)
> +{
> +	struct nand_chip *chip = mtd_to_nand(mtd);
> +	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
> +	const struct nand_sdr_timings *timings;
> +
> +	timings = nand_get_sdr_timings(conf);
> +	if (IS_ERR(timings))
> +		return PTR_ERR(timings);
> +
> +	if (csline == NAND_DATA_IFACE_CHECK_ONLY)
> +		return 0;
> +
> +	tegra_nand_setup_timing(ctrl, timings);

Is this indirection really needed?

> +
> +	return 0;
> +}
> +
> +static int tegra_nand_chips_init(struct device *dev,
> +				 struct tegra_nand_controller *ctrl)
> +{
> +	struct device_node *np = dev->of_node;
> +	struct device_node *np_nand;
> +	int nchips = of_get_child_count(np);
> +	struct tegra_nand_chip *nand;
> +	struct mtd_info *mtd;
> +	struct nand_chip *chip;
> +	unsigned long config, bch_config = 0;
> +	int bits_per_step;
> +	int err;
> +
> +	if (nchips != 1) {
> +		dev_err(dev, "currently only one NAND chip supported\n");
> +		return -EINVAL;
> +	}
> +
> +	np_nand = of_get_next_child(np, NULL);
> +
> +	nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
> +	if (!nand) {
> +		dev_err(dev, "could not allocate chip structure\n");
> +		return -ENOMEM;
> +	}
> +
> +	nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW);
> +
> +	if (IS_ERR(nand->wp_gpio)) {
> +		err = PTR_ERR(nand->wp_gpio);
> +		dev_err(dev, "failed to request WP GPIO: %d\n", err);
> +		return err;
> +	}
> +
> +	chip = &nand->chip;
> +	chip->controller = &ctrl->controller;
> +	ctrl->chip = chip;
> +
> +	mtd = nand_to_mtd(chip);
> +
> +	mtd->dev.parent = dev;
> +	mtd->name = "tegra_nand";
> +	mtd->owner = THIS_MODULE;
> +
> +	nand_set_flash_node(chip, np_nand);
> +
> +	chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER;
> +	chip->exec_op = tegra_nand_exec_op;
> +	chip->select_chip = tegra_nand_select_chip;
> +	chip->setup_data_interface = tegra_nand_setup_data_interface;
> +
> +	err = nand_scan_ident(mtd, 1, NULL);
> +	if (err)
> +		return err;
> +
> +	if (chip->bbt_options & NAND_BBT_USE_FLASH)
> +		chip->bbt_options |= NAND_BBT_NO_OOB;
> +
> +	chip->ecc.mode = NAND_ECC_HW;
> +	if (!chip->ecc.size)
> +		chip->ecc.size = 512;
> +	if (chip->ecc.size != 512)
> +		return -EINVAL;

This is useless. You can force ecc.size to 512 and forget about the DT
property (please update the DT bindings).

> +
> +	chip->ecc.read_page = tegra_nand_read_page_hwecc;
> +	chip->ecc.write_page = tegra_nand_write_page_hwecc;
> +	/* Not functional for unknown reason...
> +	chip->ecc.read_page_raw = tegra_nand_read_page;
> +	chip->ecc.write_page_raw = tegra_nand_write_page;

Please add the _raw suffix to these helpers.

> +	*/
> +	config = readl(ctrl->regs + CFG);
> +	config |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4;
> +
> +	if (chip->options & NAND_BUSWIDTH_16)
> +		config |= CFG_BUS_WIDTH_16;
> +
> +	switch (chip->ecc.algo) {
> +	case NAND_ECC_RS:
> +		bits_per_step = BITS_PER_STEP_RS * chip->ecc.strength;
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_rs_ops);
> +		switch (chip->ecc.strength) {
> +		case 4:
> +			config |= CFG_ECC_SEL | CFG_TVAL_4;
> +			break;
> +		case 6:
> +			config |= CFG_ECC_SEL | CFG_TVAL_6;
> +			break;
> +		case 8:
> +			config |= CFG_ECC_SEL | CFG_TVAL_8;
> +			break;
> +		default:
> +			dev_err(dev, "ECC strength %d not supported\n",
> +				chip->ecc.strength);
> +			return -EINVAL;
> +		}
> +		break;
> +	case NAND_ECC_BCH:
> +		bits_per_step = BITS_PER_STEP_BCH * chip->ecc.strength;
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_bch_ops);
> +		switch (chip->ecc.strength) {
> +		case 4:
> +			bch_config = BCH_TVAL_4;
> +			break;
> +		case 8:
> +			bch_config = BCH_TVAL_8;
> +			break;
> +		case 14:
> +			bch_config = BCH_TVAL_14;
> +			break;
> +		case 16:
> +			bch_config = BCH_TVAL_16;
> +			break;
> +		default:
> +			dev_err(dev, "ECC strength %d not supported\n",
> +				chip->ecc.strength);
> +			return -EINVAL;
> +		}
> +		break;
> +	default:
> +		dev_err(dev, "ECC algorithm not supported\n");
> +		return -EINVAL;
> +	}
> +
> +	chip->ecc.bytes = DIV_ROUND_UP(bits_per_step, 8);
> +
> +	switch (mtd->writesize) {
> +	case 256:
> +		config |= CFG_PS_256;
> +		break;
> +	case 512:
> +		config |= CFG_PS_512;
> +		break;
> +	case 1024:
> +		config |= CFG_PS_1024;
> +		break;
> +	case 2048:
> +		config |= CFG_PS_2048;
> +		break;
> +	case 4096:
> +		config |= CFG_PS_4096;
> +		break;
> +	default:
> +		dev_err(dev, "unhandled writesize %d\n", mtd->writesize);
> +		return -ENODEV;
> +	}
> +
> +	writel(config, ctrl->regs + CFG);
> +	writel(bch_config, ctrl->regs + BCH_CONFIG);
> +
> +	err = nand_scan_tail(mtd);
> +	if (err)
> +		return err;
> +
> +	config |= CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
> +	writel(config, ctrl->regs + CFG);
> +
> +	err = mtd_device_register(mtd, NULL, 0);
> +	if (err)

Missing nand_cleanup() in the error path.

> +		return err;
> +
> +	return 0;
> +}
> +

[...]

^ permalink raw reply

* Re: [RESEND PATCH 2/5] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Miquel Raynal @ 2018-05-27 22:04 UTC (permalink / raw)
  To: Stefan Agner
  Cc: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd, dev, richard,
	marcel, krzk, digetx, benjamin.lindqvist, jonathanh, pdeschrijver,
	pgaikwad, mirza.krak, linux-mtd, linux-tegra, devicetree,
	linux-kernel, linux-clk
In-Reply-To: <86fdf19ec92b732709732fb60199f16488b4b727.1526990589.git.stefan@agner.ch>

Hi Stefan,

I just see your v2 while I'm sending my review on the driver, will
probably wait for v4 then ;)

Thanks for the work though!
Miquèl

On Tue, 22 May 2018 14:07:06 +0200, Stefan Agner <stefan@agner.ch>
wrote:

> Add support for the NAND flash controller found on NVIDIA
> Tegra 2 SoCs. This implementation does not make use of the
> command queue feature. Regular operations/data transfers are
> done in PIO mode. Page read/writes with hardware ECC make
> use of the DMA for data transfer.
> 
> Signed-off-by: Lucas Stach <dev@lynxeye.de>
> Signed-off-by: Stefan Agner <stefan@agner.ch>
> ---

[...]

> --- /dev/null
> +++ b/drivers/mtd/nand/raw/tegra_nand.c
> @@ -0,0 +1,915 @@
> +/*
> + * Copyright (C) 2018 Stefan Agner <stefan@agner.ch>
> + * Copyright (C) 2014-2015 Lucas Stach <dev@lynxeye.de>
> + * Copyright (C) 2012 Avionic Design GmbH
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.

Please use SPDX tag.

> + */
> +
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/mtd/rawnand.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +
> +#define CMD					0x00
> +#define   CMD_GO				(1 << 31)
> +#define   CMD_CLE				(1 << 30)
> +#define   CMD_ALE				(1 << 29)
> +#define   CMD_PIO				(1 << 28)
> +#define   CMD_TX				(1 << 27)
> +#define   CMD_RX				(1 << 26)

Please use the BIT(x) macro instead of (1 << x)

> +#define   CMD_SEC_CMD				(1 << 25)
> +#define   CMD_AFT_DAT				(1 << 24)
> +#define   CMD_TRANS_SIZE(x)			(((x - 1) & 0xf) << 20)
> +#define   CMD_A_VALID				(1 << 19)
> +#define   CMD_B_VALID				(1 << 18)
> +#define   CMD_RD_STATUS_CHK			(1 << 17)
> +#define   CMD_RBSY_CHK				(1 << 16)
> +#define   CMD_CE(x)				(1 << (8 + ((x) & 0x7)))
> +#define   CMD_CLE_SIZE(x)			(((x - 1) & 0x3) << 4)
> +#define   CMD_ALE_SIZE(x)			(((x - 1) & 0xf) << 0)
> +

[...]

> +static int tegra_nand_cmd(struct nand_chip *chip,
> +			 const struct nand_subop *subop)
> +{
> +	const struct nand_op_instr *instr;
> +	const struct nand_op_instr *instr_data_in = NULL;
> +	struct mtd_info *mtd = nand_to_mtd(chip);
> +	struct tegra_nand *nand = to_tegra_nand(mtd);
> +	unsigned int op_id = -1, trfr_in_sz = 0, trfr_out_sz = 0, offset = 0;
> +	bool first_cmd = true;
> +	bool force8bit;
> +	u32 cmd = 0;
> +	u32 value;
> +
> +	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
> +		unsigned int naddrs, i;
> +		const u8 *addrs;
> +		u32 addr1 = 0, addr2 = 0;
> +
> +		instr = &subop->instrs[op_id];
> +
> +		switch (instr->type) {
> +		case NAND_OP_CMD_INSTR:
> +			if (first_cmd) {
> +				cmd |= CMD_CLE;
> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_1);
> +			} else {
> +				cmd |= CMD_SEC_CMD;
> +				writel(instr->ctx.cmd.opcode, nand->regs + CMD_2);
> +			}
> +			first_cmd = false;
> +			break;
> +		case NAND_OP_ADDR_INSTR:
> +			offset = nand_subop_get_addr_start_off(subop, op_id);
> +			naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
> +			addrs = &instr->ctx.addr.addrs[offset];
> +
> +			cmd |= CMD_ALE | CMD_ALE_SIZE(naddrs);
> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
> +				addr1 |= *addrs++ << (8 * i);
> +			naddrs -= i;
> +			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
> +				addr2 |= *addrs++ << (8 * i);
> +			writel(addr1, nand->regs + ADDR_1);
> +			writel(addr2, nand->regs + ADDR_2);
> +			break;
> +
> +		case NAND_OP_DATA_IN_INSTR:
> +			trfr_in_sz = nand_subop_get_data_len(subop, op_id);
> +			offset = nand_subop_get_data_start_off(subop, op_id);
> +
> +			cmd |= CMD_TRANS_SIZE(trfr_in_sz) | CMD_PIO | CMD_RX | CMD_A_VALID;
> +
> +			instr_data_in = instr;
> +			break;
> +
> +		case NAND_OP_DATA_OUT_INSTR:
> +			trfr_out_sz = nand_subop_get_data_len(subop, op_id);
> +			offset = nand_subop_get_data_start_off(subop, op_id);
> +			trfr_out_sz = min_t(size_t, trfr_out_sz, 4);
> +
> +			cmd |= CMD_TRANS_SIZE(trfr_out_sz) | CMD_PIO | CMD_TX | CMD_A_VALID;
> +
> +			memcpy(&value, instr->ctx.data.buf.out + offset, trfr_out_sz);
> +			writel(value, nand->regs + RESP);
> +
> +			break;
> +		case NAND_OP_WAITRDY_INSTR:
> +			cmd |= CMD_RBSY_CHK;
> +			break;
> +
> +		}
> +	}
> +
> +
> +	cmd |= CMD_GO | CMD_CE(nand->cur_chip);
> +	writel(cmd, nand->regs + CMD);
> +	wait_for_completion(&nand->command_complete);

_timeout?

> +
> +	if (instr_data_in) {
> +		u32 value;
> +		size_t n = min_t(size_t, trfr_in_sz, 4);
> +
> +		value = readl(nand->regs + RESP);
> +		memcpy(instr_data_in->ctx.data.buf.in + offset, &value, n);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER(
> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
> +		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)),
> +	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
> +		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
> +		NAND_OP_PARSER_PAT_CMD_ELEM(true),
> +		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
> +		NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)),
> +	);
> +
> +static int tegra_nand_exec_op(struct nand_chip *chip,
> +			     const struct nand_operation *op,
> +			     bool check_only)
> +{
> +	return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op,
> +				      check_only);
> +}
> +static void tegra_nand_select_chip(struct mtd_info *mtd, int chip)
> +{
> +	struct tegra_nand *nand = to_tegra_nand(mtd);
> +
> +	nand->cur_chip = chip;

You should probably save the timings configuration and apply them back
here in case of using different chips.

> +}
> +
> +static u32 tegra_nand_fill_address(struct mtd_info *mtd, struct nand_chip *chip,
> +				   int page)
> +{
> +	struct tegra_nand *nand = to_tegra_nand(mtd);
> +
> +	/* Lower 16-bits are column, always 0 */
> +	writel(page << 16, nand->regs + ADDR_1);
> +
> +	if (chip->options & NAND_ROW_ADDR_3) {
> +		writel(page >> 16, nand->regs + ADDR_2);
> +		return 5;
> +	}
> +
> +	return 4;
> +}
> +
> +static int tegra_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
> +				uint8_t *buf, int oob_required, int page)
> +{
> +	struct tegra_nand *nand = to_tegra_nand(mtd);
> +	u32 value, addrs;
> +
> +	writel(NAND_CMD_READ0, nand->regs + CMD_1);
> +	writel(NAND_CMD_READSTART, nand->regs + CMD_2);
> +
> +	addrs = tegra_nand_fill_address(mtd, chip, page);
> +
> +	value = readl(nand->regs + CFG);
> +	value |= CFG_HW_ECC | CFG_ERR_COR;
> +	writel(value, nand->regs + CFG);
> +
> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
> +	writel(nand->data_dma, nand->regs + DATA_PTR);
> +
> +	if (oob_required) {
> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
> +		       nand->regs + DMA_CFG_B);
> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
> +	} else {
> +		writel(0, nand->regs + DMA_CFG_B);
> +		writel(0, nand->regs + TAG_PTR);
> +	}
> +
> +	value = DMA_CTRL_GO | DMA_CTRL_IN | DMA_CTRL_PERF_EN |
> +		DMA_CTRL_REUSE | DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
> +	if (oob_required)
> +		value |= DMA_CTRL_EN_B;
> +	writel(value, nand->regs + DMA_CTRL);
> +
> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
> +		CMD_RBSY_CHK | CMD_GO | CMD_RX | CMD_TRANS_SIZE(9) |
> +		CMD_A_VALID | CMD_CE(nand->cur_chip);
> +	if (oob_required)
> +		value |= CMD_B_VALID;
> +	writel(value, nand->regs + CMD);
> +
> +	wait_for_completion(&nand->command_complete);
> +	wait_for_completion(&nand->dma_complete);
> +
> +	if (oob_required) {
> +		struct mtd_oob_region oobregion;
> +
> +		mtd_ooblayout_free(mtd, 0, &oobregion);

Don't you want to save the oobregion parameters once and then just
refer to them?

> +		memcpy(chip->oob_poi, nand->oob_buf + oobregion.offset,
> +		       mtd_ooblayout_count_freebytes(mtd));
> +	}
> +	memcpy(buf, nand->data_buf, mtd->writesize);
> +
> +	value = readl(nand->regs + CFG);
> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
> +	writel(value, nand->regs + CFG);
> +
> +	value = readl(nand->regs + DEC_STATUS);
> +	if (value & DEC_STATUS_A_ECC_FAIL) {
> +		/*
> +		 * The ECC isn't smart enough to figure out if a page is
> +		 * completely erased and flags an error in this case. So we
> +		 * check the read data here to figure out if it's a legitimate
> +		 * error or a false positive.
> +		 */
> +		int i, err;
> +		int flips_threshold = chip->ecc.strength / 2;
> +		int max_bitflips = 0;
> +
> +		for (i = 0; i < chip->ecc.steps; i++) {
> +			u8 *data = buf + (chip->ecc.size * i);
> +			err = nand_check_erased_ecc_chunk(data, chip->ecc.size,

Are you sure the data was uncorrected there? I bet you have corrected
data in chip->ecc.size and should re-read the page with the raw
helpers before using nand_check_erased_ecc_chunk().
 
> +							  NULL, 0,
> +							  NULL, 0,
> +							  flips_threshold);

I think you should use chip->ecc.strength instead of flips_threshold
(and remove it).

> +			if (err < 0)
> +				return err;

In case of ECC failure you should increment ecc_stats.failed.

> +
> +			max_bitflips += max_bitflips;

max_bitflipts = max_t(unsigned int, max_bitflipts, err);

> +		}
> +
> +		return max_bitflips;
> +	}
> +
> +	if (nand->last_read_error) {
> +		int max_corr_cnt, corr_sec_flag;
> +
> +		value = readl(nand->regs + DEC_STAT_BUF);
> +		corr_sec_flag = (value & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >>
> +				DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT;
> +		max_corr_cnt = (value & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >>
> +			       DEC_STAT_BUF_MAX_CORR_CNT_SHIFT;
> +
> +		/*
> +		 * The value returned in the register is the maximum of
> +		 * bitflips encountered in any of the ECC regions. As there is
> +		 * no way to get the number of bitflips in a specific regions
> +		 * we are not able to deliver correct stats but instead
> +		 * overestimate the number of corrected bitflips by assuming
> +		 * that all regions where errors have been corrected
> +		 * encountered the maximum number of bitflips.
> +		 */
> +		mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag);

That's bad. But okay if we don't have the information.

> +		nand->last_read_error = false;
> +		return value;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
> +				 const uint8_t *buf, int oob_required, int page)
> +{
> +	struct tegra_nand *nand = to_tegra_nand(mtd);
> +	u32 value, addrs;
> +
> +	writel(NAND_CMD_SEQIN, nand->regs + CMD_1);
> +	writel(NAND_CMD_PAGEPROG, nand->regs + CMD_2);
> +
> +	addrs = tegra_nand_fill_address(mtd, chip, page);
> +
> +	value = readl(nand->regs + CFG);
> +	value |= CFG_HW_ECC | CFG_ERR_COR;
> +	writel(value, nand->regs + CFG);

You might want to test with the _relaxed() operators?

> +
> +	memcpy(nand->data_buf, buf, mtd->writesize);
> +
> +	writel(mtd->writesize - 1, nand->regs + DMA_CFG_A);
> +	writel(nand->data_dma, nand->regs + DATA_PTR);
> +
> +	if (oob_required) {
> +		struct mtd_oob_region oobregion;
> +
> +		mtd_ooblayout_free(mtd, 0, &oobregion);
> +		memcpy(nand->oob_buf, chip->oob_poi + oobregion.offset,
> +		       mtd_ooblayout_count_freebytes(mtd));
> +		writel(mtd_ooblayout_count_freebytes(mtd) - 1,
> +		       nand->regs + DMA_CFG_B);
> +		writel(nand->oob_dma, nand->regs + TAG_PTR);
> +	} else {
> +		writel(0, nand->regs + DMA_CFG_B);
> +		writel(0, nand->regs + TAG_PTR);
> +	}
> +
> +	value = DMA_CTRL_GO | DMA_CTRL_OUT | DMA_CTRL_PERF_EN |
> +		DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
> +		DMA_CTRL_BURST_8 | DMA_CTRL_EN_A;
> +	if (oob_required)
> +		value |= DMA_CTRL_EN_B;

Line here

> +	writel(value, nand->regs + DMA_CTRL);
> +
> +	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
> +		CMD_AFT_DAT | CMD_RBSY_CHK | CMD_GO | CMD_TX | CMD_A_VALID |
> +		CMD_TRANS_SIZE(9) | CMD_CE(nand->cur_chip);
> +	if (oob_required)
> +		value |= CMD_B_VALID;

Line here

> +	writel(value, nand->regs + CMD);
> +
> +	wait_for_completion(&nand->command_complete);
> +	wait_for_completion(&nand->dma_complete);
> +
> +	value = readl(nand->regs + CFG);
> +	value &= ~(CFG_HW_ECC | CFG_ERR_COR);
> +	writel(value, nand->regs + CFG);
> +
> +	return 0;
> +}
> +
> +static void tegra_nand_setup_timing(struct tegra_nand *nand, int mode)
> +{
> +	/*
> +	 * The period (and all other timings in this function) is in ps,
> +	 * so need to take care here to avoid integer overflows.

You might wanna check __DIVIDE, PSEC_TO_NSEC and PSEC_TO_MSEC macros in
rawnand.h. You could use them in the following derivations.

> +	 */
> +	unsigned int rate = clk_get_rate(nand->clk) / 1000000;
> +	unsigned int period = DIV_ROUND_UP(1000000, rate);
> +	const struct nand_sdr_timings *timings;
> +	u32 val, reg = 0;
> +
> +	timings = onfi_async_timing_mode_to_sdr_timings(mode);
> +
> +	val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min,
> +				timings->tRC_min), period);
> +	if (val > 2)
> +		val -= 3;
> +	reg |= TIMING_TCR_TAR_TRR(val);
> +
> +	val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min),
> +				   max(timings->tALS_min, timings->tALH_min)),

Is the second line aligned correctly?

> +			   period);
> +	if (val > 1)
> +		val -= 2;

This is weird and I would recommend a comment.

> +	reg |= TIMING_TCS(val);
> +
> +	val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000,
> +			   period);
> +	reg |= TIMING_TRP(val) | TIMING_TRP_RESP(val);
> +
> +	reg |= TIMING_TWB(DIV_ROUND_UP(timings->tWB_max, period));
> +	reg |= TIMING_TWHR(DIV_ROUND_UP(timings->tWHR_min, period));
> +	reg |= TIMING_TWH(DIV_ROUND_UP(timings->tWH_min, period));
> +	reg |= TIMING_TWP(DIV_ROUND_UP(timings->tWP_min, period));
> +	reg |= TIMING_TRH(DIV_ROUND_UP(timings->tRHW_min, period));
> +
> +	writel(reg, nand->regs + TIMING_1);
> +
> +	val = DIV_ROUND_UP(timings->tADL_min, period);
> +	if (val > 2)
> +		val -= 3;

Ditto

> +	reg = TIMING_TADL(val);
> +
> +	writel(reg, nand->regs + TIMING_2);
> +}
> +
> +static void tegra_nand_setup_chiptiming(struct tegra_nand *nand)
> +{
> +	struct nand_chip *chip = &nand->chip;
> +	int mode;
> +
> +	mode = onfi_get_async_timing_mode(chip);
> +	if (mode == ONFI_TIMING_MODE_UNKNOWN)
> +		mode = chip->onfi_timing_mode_default;
> +	else
> +		mode = fls(mode);
> +
> +	tegra_nand_setup_timing(nand, mode);
> +}

You can drop this function and use tegra_nand_setup_timing directly as
hook for ->setup_data_interface().

> +
> +static int tegra_nand_probe(struct platform_device *pdev)
> +{
> +	struct reset_control *rst;
> +	struct tegra_nand *nand;

Would you mind having another name for the tegra_nand structure than
just 'nand'? I found it confusing as, following Boris comment, it won't
be a 'NAND device' structure but rather more a controller structure.

> +	struct nand_chip *chip;
> +	struct mtd_info *mtd;
> +	struct resource *res;
> +	unsigned long value;

s/value/reg/ ? or something more explicit?

> +	int irq, err = 0;
> +
> +	nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL);
> +	if (!nand)
> +		return -ENOMEM;
> +
> +	nand->dev = &pdev->dev;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	nand->regs = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(nand->regs))
> +		return PTR_ERR(nand->regs);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	err = devm_request_irq(&pdev->dev, irq, tegra_nand_irq, 0,
> +			       dev_name(&pdev->dev), nand);
> +	if (err)
> +		return err;
> +
> +	rst = devm_reset_control_get(&pdev->dev, "nand");
> +	if (IS_ERR(rst))
> +		return PTR_ERR(rst);
> +
> +	nand->clk = devm_clk_get(&pdev->dev, "nand");
> +	if (IS_ERR(nand->clk))
> +		return PTR_ERR(nand->clk);
> +
> +	nand->wp_gpio = gpiod_get_optional(&pdev->dev, "wp-gpios",
> +					   GPIOD_OUT_HIGH);
> +	if (IS_ERR(nand->wp_gpio))
> +		return PTR_ERR(nand->wp_gpio);
> +
> +	err = clk_prepare_enable(nand->clk);
> +	if (err)
> +		return err;
> +
> +	reset_control_assert(rst);
> +	udelay(2);
> +	reset_control_deassert(rst);
> +
> +	value = HWSTATUS_RDSTATUS_MASK(1) | HWSTATUS_RDSTATUS_VALUE(0) |
> +		HWSTATUS_RBSY_MASK(NAND_STATUS_READY) |
> +		HWSTATUS_RBSY_VALUE(NAND_STATUS_READY);
> +	writel(NAND_CMD_STATUS, nand->regs + HWSTATUS_CMD);
> +	writel(value, nand->regs + HWSTATUS_MASK);
> +
> +	init_completion(&nand->command_complete);
> +	init_completion(&nand->dma_complete);
> +
> +	/* clear interrupts */
> +	value = readl(nand->regs + ISR);
> +	writel(value, nand->regs + ISR);
> +
> +	writel(DMA_CTRL_IS_DONE, nand->regs + DMA_CTRL);
> +
> +	/* enable interrupts */
> +	value = IER_UND | IER_OVR | IER_CMD_DONE | IER_ECC_ERR | IER_GIE;
> +	writel(value, nand->regs + IER);
> +
> +	/* reset config */
> +	writel(0, nand->regs + CFG);
> +
> +	chip = &nand->chip;
> +	mtd = nand_to_mtd(chip);
> +
> +	mtd->dev.parent = &pdev->dev;
> +	mtd->name = "tegra_nand";

I just figured it was undocumented (yet) but you could have a label
string property in your nand DT node that tells you the name of the
MTD device instead of something too generic like tegra_nand.

> +	mtd->owner = THIS_MODULE;
> +
> +	nand_set_flash_node(chip, pdev->dev.of_node);
> +	nand_set_controller_data(chip, nand);
> +
> +	chip->options = NAND_NO_SUBPAGE_WRITE;
> +	chip->exec_op = tegra_nand_exec_op;
> +	chip->select_chip = tegra_nand_select_chip;
> +	tegra_nand_setup_timing(nand, 0);

You really should implement ->setup_data_interface() and let the core
handle the timings issue entirely (mind that chipnr is not the NAND
chip id but more the CS id asserted for the pointed NAND chip).
 
> +
> +	err = nand_scan_ident(mtd, 1, NULL);
> +	if (err)
> +		goto err_disable_clk;
> +
> +	if (chip->bbt_options & NAND_BBT_USE_FLASH)
> +		chip->bbt_options |= NAND_BBT_NO_OOB;
> +
> +	nand->data_buf = dmam_alloc_coherent(&pdev->dev, mtd->writesize,
> +					    &nand->data_dma, GFP_KERNEL);

Do you need these buffers before nand_scan_tail() or could you simply
use the ones allocated by the core right after?

> +	if (!nand->data_buf) {
> +		err = -ENOMEM;
> +		goto err_disable_clk;
> +	}
> +
> +	nand->oob_buf = dmam_alloc_coherent(&pdev->dev, mtd->oobsize,
> +					    &nand->oob_dma, GFP_KERNEL);
> +	if (!nand->oob_buf) {
> +		err = -ENOMEM;
> +		goto err_disable_clk;
> +	}
> +
> +	chip->ecc.mode = NAND_ECC_HW;
> +	chip->ecc.size = 512;
> +	chip->ecc.read_page = tegra_nand_read_page;
> +	chip->ecc.write_page = tegra_nand_write_page;
> +
> +	value = readl(nand->regs + CFG);
> +	value |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4 |
> +		 CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
> +
> +	if (chip->options & NAND_BUSWIDTH_16)
> +		value |= CFG_BUS_WIDTH_16;
> +
> +	switch (mtd->oobsize) {
> +	case 16:
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_16_ops);
> +		chip->ecc.strength = 1;
> +		chip->ecc.bytes = 4;
> +		break;
> +	case 64:
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_64_ops);
> +		chip->ecc.strength = 8;
> +		chip->ecc.bytes = 18;
> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
> +		break;
> +	case 128:
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_128_ops);
> +		chip->ecc.strength = 8;
> +		chip->ecc.bytes = 18;
> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
> +		break;
> +	case 224:
> +		mtd_set_ooblayout(mtd, &tegra_nand_oob_224_ops);
> +		chip->ecc.strength = 8;
> +		chip->ecc.bytes = 18;
> +		value |= CFG_ECC_SEL | CFG_TVAL_8;
> +		break;
> +	default:
> +		dev_err(&pdev->dev, "unhandled OOB size %d\n", mtd->oobsize);
> +		err = -ENODEV;
> +		goto err_disable_clk;
> +	}
> +
> +	switch (mtd->writesize) {
> +	case 256:
> +		value |= CFG_PS_256;
> +		break;
> +	case 512:
> +		value |= CFG_PS_512;
> +		break;
> +	case 1024:
> +		value |= CFG_PS_1024;
> +		break;
> +	case 2048:
> +		value |= CFG_PS_2048;
> +		break;
> +	case 4096:
> +		value |= CFG_PS_4096;
> +		break;
> +	default:
> +		dev_err(&pdev->dev, "unhandled writesize %d\n", mtd->writesize);
> +		err = -ENODEV;
> +		goto err_disable_clk;
> +	}
> +
> +	writel(value, nand->regs + CFG);
> +
> +	tegra_nand_setup_chiptiming(nand);
> +
> +	err = nand_scan_tail(mtd);
> +	if (err)
> +		goto err_disable_clk;
> +
> +	err = mtd_device_register(mtd, NULL, 0);
> +	if (err)
> +		goto err_cleanup_nand;
> +
> +	platform_set_drvdata(pdev, nand);
> +
> +	return 0;
> +
> +err_cleanup_nand:
> +	nand_cleanup(chip);
> +err_disable_clk:
> +	clk_disable_unprepare(nand->clk);
> +	return err;
> +}
> +
> +static int tegra_nand_remove(struct platform_device *pdev)
> +{
> +	struct tegra_nand *nand = platform_get_drvdata(pdev);
> +
> +	nand_release(nand_to_mtd(&nand->chip));
> +
> +	clk_disable_unprepare(nand->clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id tegra_nand_of_match[] = {
> +	{ .compatible = "nvidia,tegra20-nand" },
> +	{ /* sentinel */ }
> +};
> +
> +static struct platform_driver tegra_nand_driver = {
> +	.driver = {
> +		.name = "tegra-nand",
> +		.of_match_table = tegra_nand_of_match,
> +	},
> +	.probe = tegra_nand_probe,
> +	.remove = tegra_nand_remove,
> +};
> +module_platform_driver(tegra_nand_driver);
> +
> +MODULE_DESCRIPTION("NVIDIA Tegra NAND driver");
> +MODULE_AUTHOR("Thierry Reding <thierry.reding@nvidia.com>");
> +MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
> +MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DEVICE_TABLE(of, tegra_nand_of_match);

^ permalink raw reply

* [PATCH v2 6/6] ARM: tegra: enable NAND flash on Colibri T20
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

From: Lucas Stach <dev@lynxeye.de>

This enables the on-module ONFI conformant NAND flash.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 arch/arm/boot/dts/tegra20-colibri-512.dtsi | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20-colibri-512.dtsi b/arch/arm/boot/dts/tegra20-colibri-512.dtsi
index 5c202b3e3bb1..ba8c0a535d6e 100644
--- a/arch/arm/boot/dts/tegra20-colibri-512.dtsi
+++ b/arch/arm/boot/dts/tegra20-colibri-512.dtsi
@@ -462,6 +462,22 @@
 		};
 	};
 
+	nand@70008000 {
+		status = "okay";
+
+		nand-chip@0 {
+			reg = <0>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			nand-bus-width = <8>;
+			nand-on-flash-bbt;
+			nand-ecc-algo = "bch";
+			nand-ecc-step-size = <512>;
+			nand-ecc-strength = <16>;
+			wp-gpios = <&gpio TEGRA_GPIO(S, 0) GPIO_ACTIVE_LOW>;
+		};
+	};
+
 	usb@c5004000 {
 		status = "okay";
 		nvidia,phy-reset-gpio = <&gpio TEGRA_GPIO(V, 1)
-- 
2.17.0

^ permalink raw reply related

* [PATCH v2 5/6] ARM: tegra: add Tegra20 NAND flash controller node
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

From: Lucas Stach <dev@lynxeye.de>

Add basic controller description to be extended
by individual boards.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 arch/arm/boot/dts/tegra20.dtsi | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 0a7136462a1a..b106e4912b21 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -425,6 +425,19 @@
 		status = "disabled";
 	};
 
+	nand: nand@70008000 {
+		compatible = "nvidia,tegra20-nand";
+		reg = <0x70008000 0x100>;
+		interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
+		clock-names = "nand";
+		resets = <&tegra_car 13>;
+		reset-names = "nand";
+		status = "disabled";
+		#address-cells = <1>;
+		#size-cells = <0>;
+	};
+
 	pwm: pwm@7000a000 {
 		compatible = "nvidia,tegra20-pwm";
 		reg = <0x7000a000 0x100>;
-- 
2.17.0

^ permalink raw reply related

* [PATCH v2 4/6] clk: tegra20: init NDFLASH clock to sensible rate
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

From: Lucas Stach <dev@lynxeye.de>

Set up the NAND Flash controller clock to run at 150MHz
instead of the rate set by the bootloader. This is a
conservative rate which also yields good performance.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 drivers/clk/tegra/clk-tegra20.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index 0ee56dd04cec..dff8c425cd28 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -1049,6 +1049,7 @@ static struct tegra_clk_init_table init_table[] __initdata = {
 	{ TEGRA20_CLK_GR2D, TEGRA20_CLK_PLL_C, 300000000, 0 },
 	{ TEGRA20_CLK_GR3D, TEGRA20_CLK_PLL_C, 300000000, 0 },
 	{ TEGRA20_CLK_VDE, TEGRA20_CLK_CLK_MAX, 300000000, 0 },
+	{ TEGRA20_CLK_NDFLASH, TEGRA20_CLK_PLL_P, 150000000, 0 },
 	/* must be the last entry */
 	{ TEGRA20_CLK_CLK_MAX, TEGRA20_CLK_CLK_MAX, 0, 0 },
 };
-- 
2.17.0

^ permalink raw reply related

* [PATCH v2 3/6] mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

Add support for the NAND flash controller found on NVIDIA
Tegra 2 SoCs. This implementation does not make use of the
command queue feature. Regular operations/data transfers are
done in PIO mode. Page read/writes with hardware ECC make
use of the DMA for data transfer.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 MAINTAINERS                       |   7 +
 drivers/mtd/nand/raw/Kconfig      |   6 +
 drivers/mtd/nand/raw/Makefile     |   1 +
 drivers/mtd/nand/raw/tegra_nand.c | 999 ++++++++++++++++++++++++++++++
 4 files changed, 1013 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/tegra_nand.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 58b9861ccf99..8cbbb7111742 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13844,6 +13844,13 @@ M:	Laxman Dewangan <ldewangan@nvidia.com>
 S:	Supported
 F:	drivers/input/keyboard/tegra-kbc.c
 
+TEGRA NAND DRIVER
+M:	Stefan Agner <stefan@agner.ch>
+M:	Lucas Stach <dev@lynxeye.de>
+S:	Maintained
+F:	Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
+F:	drivers/mtd/nand/raw/tegra_nand.c
+
 TEGRA PWM DRIVER
 M:	Thierry Reding <thierry.reding@gmail.com>
 S:	Supported
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 19a2b283fbbe..012c63c6ab47 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -534,4 +534,10 @@ config MTD_NAND_MTK
 	  Enables support for NAND controller on MTK SoCs.
 	  This controller is found on mt27xx, mt81xx, mt65xx SoCs.
 
+config MTD_NAND_TEGRA
+	tristate "Support for NAND on NVIDIA Tegra"
+	depends on ARCH_TEGRA || COMPILE_TEST
+	help
+	  Enables support for NAND flash on NVIDIA Tegra SoC based boards.
+
 endif # MTD_NAND
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 165b7ef9e9a1..d5a5f9832b88 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504)	        += hisi504_nand.o
 obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand/
 obj-$(CONFIG_MTD_NAND_QCOM)		+= qcom_nandc.o
 obj-$(CONFIG_MTD_NAND_MTK)		+= mtk_ecc.o mtk_nand.o
+obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
 
 nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
 nand-objs += nand_amd.o
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
new file mode 100644
index 000000000000..1a0833d97472
--- /dev/null
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -0,0 +1,999 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Stefan Agner <stefan@agner.ch>
+ * Copyright (C) 2014-2015 Lucas Stach <dev@lynxeye.de>
+ * Copyright (C) 2012 Avionic Design GmbH
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define CMD					0x00
+#define   CMD_GO				(1 << 31)
+#define   CMD_CLE				(1 << 30)
+#define   CMD_ALE				(1 << 29)
+#define   CMD_PIO				(1 << 28)
+#define   CMD_TX				(1 << 27)
+#define   CMD_RX				(1 << 26)
+#define   CMD_SEC_CMD				(1 << 25)
+#define   CMD_AFT_DAT				(1 << 24)
+#define   CMD_TRANS_SIZE(x)			(((x - 1) & 0xf) << 20)
+#define   CMD_A_VALID				(1 << 19)
+#define   CMD_B_VALID				(1 << 18)
+#define   CMD_RD_STATUS_CHK			(1 << 17)
+#define   CMD_RBSY_CHK				(1 << 16)
+#define   CMD_CE(x)				(1 << (8 + ((x) & 0x7)))
+#define   CMD_CLE_SIZE(x)			(((x - 1) & 0x3) << 4)
+#define   CMD_ALE_SIZE(x)			(((x - 1) & 0xf) << 0)
+
+#define STATUS					0x04
+
+#define ISR					0x08
+#define   ISR_CORRFAIL_ERR			(1 << 24)
+#define   ISR_UND				(1 << 7)
+#define   ISR_OVR				(1 << 6)
+#define   ISR_CMD_DONE				(1 << 5)
+#define   ISR_ECC_ERR				(1 << 4)
+
+#define IER					0x0c
+#define   IER_ERR_TRIG_VAL(x)			(((x) & 0xf) << 16)
+#define   IER_UND				(1 << 7)
+#define   IER_OVR				(1 << 6)
+#define   IER_CMD_DONE				(1 << 5)
+#define   IER_ECC_ERR				(1 << 4)
+#define   IER_GIE				(1 << 0)
+
+#define CFG					0x10
+#define   CFG_HW_ECC				(1 << 31)
+#define   CFG_ECC_SEL				(1 << 30)
+#define   CFG_ERR_COR				(1 << 29)
+#define   CFG_PIPE_EN				(1 << 28)
+#define   CFG_TVAL_4				(0 << 24)
+#define   CFG_TVAL_6				(1 << 24)
+#define   CFG_TVAL_8				(2 << 24)
+#define   CFG_SKIP_SPARE			(1 << 23)
+#define   CFG_BUS_WIDTH_8			(0 << 21)
+#define   CFG_BUS_WIDTH_16			(1 << 21)
+#define   CFG_COM_BSY				(1 << 20)
+#define   CFG_PS_256				(0 << 16)
+#define   CFG_PS_512				(1 << 16)
+#define   CFG_PS_1024				(2 << 16)
+#define   CFG_PS_2048				(3 << 16)
+#define   CFG_PS_4096				(4 << 16)
+#define   CFG_SKIP_SPARE_SIZE_4			(0 << 14)
+#define   CFG_SKIP_SPARE_SIZE_8			(1 << 14)
+#define   CFG_SKIP_SPARE_SIZE_12		(2 << 14)
+#define   CFG_SKIP_SPARE_SIZE_16		(3 << 14)
+#define   CFG_TAG_BYTE_SIZE(x)			((x) & 0xff)
+
+#define TIMING_1				0x14
+#define   TIMING_TRP_RESP(x)			(((x) & 0xf) << 28)
+#define   TIMING_TWB(x)				(((x) & 0xf) << 24)
+#define   TIMING_TCR_TAR_TRR(x)			(((x) & 0xf) << 20)
+#define   TIMING_TWHR(x)			(((x) & 0xf) << 16)
+#define   TIMING_TCS(x)				(((x) & 0x3) << 14)
+#define   TIMING_TWH(x)				(((x) & 0x3) << 12)
+#define   TIMING_TWP(x)				(((x) & 0xf) <<  8)
+#define   TIMING_TRH(x)				(((x) & 0xf) <<  4)
+#define   TIMING_TRP(x)				(((x) & 0xf) <<  0)
+
+#define RESP					0x18
+
+#define TIMING_2				0x1c
+#define   TIMING_TADL(x)			((x) & 0xf)
+
+#define CMD_1					0x20
+#define CMD_2					0x24
+#define ADDR_1					0x28
+#define ADDR_2					0x2c
+
+#define DMA_CTRL				0x30
+#define   DMA_CTRL_GO				(1 << 31)
+#define   DMA_CTRL_IN				(0 << 30)
+#define   DMA_CTRL_OUT				(1 << 30)
+#define   DMA_CTRL_PERF_EN			(1 << 29)
+#define   DMA_CTRL_IE_DONE			(1 << 28)
+#define   DMA_CTRL_REUSE			(1 << 27)
+#define   DMA_CTRL_BURST_1			(2 << 24)
+#define   DMA_CTRL_BURST_4			(3 << 24)
+#define   DMA_CTRL_BURST_8			(4 << 24)
+#define   DMA_CTRL_BURST_16			(5 << 24)
+#define   DMA_CTRL_IS_DONE			(1 << 20)
+#define   DMA_CTRL_EN_A				(1 <<  2)
+#define   DMA_CTRL_EN_B				(1 <<  1)
+
+#define DMA_CFG_A				0x34
+#define DMA_CFG_B				0x38
+
+#define FIFO_CTRL				0x3c
+#define   FIFO_CTRL_CLR_ALL			(1 << 3)
+
+#define DATA_PTR				0x40
+#define TAG_PTR					0x44
+#define ECC_PTR					0x48
+
+#define DEC_STATUS				0x4c
+#define   DEC_STATUS_A_ECC_FAIL			(1 << 1)
+#define   DEC_STATUS_ERR_COUNT_MASK		0x00ff0000
+#define   DEC_STATUS_ERR_COUNT_SHIFT		16
+
+#define HWSTATUS_CMD				0x50
+#define HWSTATUS_MASK				0x54
+#define   HWSTATUS_RDSTATUS_MASK(x)		(((x) & 0xff) << 24)
+#define   HWSTATUS_RDSTATUS_VALUE(x)		(((x) & 0xff) << 16)
+#define   HWSTATUS_RBSY_MASK(x)			(((x) & 0xff) << 8)
+#define   HWSTATUS_RBSY_VALUE(x)		(((x) & 0xff) << 0)
+
+#define BCH_CONFIG				0xcc
+#define   BCH_ENABLE				(1 << 0)
+#define   BCH_TVAL_4				(0 << 4)
+#define   BCH_TVAL_8				(1 << 4)
+#define   BCH_TVAL_14				(2 << 4)
+#define   BCH_TVAL_16				(3 << 4)
+
+#define DEC_STAT_RESULT				0xd0
+#define DEC_STAT_BUF				0xd4
+#define   DEC_STAT_BUF_FAIL_SEC_FLAG_MASK	0xff000000
+#define   DEC_STAT_BUF_FAIL_SEC_FLAG_SHIFT	24
+#define   DEC_STAT_BUF_CORR_SEC_FLAG_MASK	0x00ff0000
+#define   DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT	16
+#define   DEC_STAT_BUF_MAX_CORR_CNT_MASK	0x00001f00
+#define   DEC_STAT_BUF_MAX_CORR_CNT_SHIFT	8
+
+#define SKIP_SPARE_BYTES	4
+#define BITS_PER_STEP_RS	18
+#define BITS_PER_STEP_BCH	13
+
+struct tegra_nand_controller {
+	struct nand_hw_control controller;
+	void __iomem *regs;
+	struct clk *clk;
+	struct device *dev;
+	struct completion command_complete;
+	struct completion dma_complete;
+	bool last_read_error;
+	int cur_chip;
+	struct nand_chip *chip;
+};
+
+struct tegra_nand_chip {
+	struct nand_chip chip;
+	struct gpio_desc *wp_gpio;
+};
+
+static inline struct tegra_nand_controller *to_tegra_ctrl(
+						struct nand_hw_control *hw_ctrl)
+{
+	return container_of(hw_ctrl, struct tegra_nand_controller, controller);
+}
+
+static int tegra_nand_ooblayout_rs_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int bytes_per_step = (BITS_PER_STEP_RS * chip->ecc.strength) / 8;
+
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = SKIP_SPARE_BYTES;
+	oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4);
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_rs_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_RS * chip->ecc.strength, 8);
+
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = SKIP_SPARE_BYTES +
+			    round_up(bytes_per_step * chip->ecc.steps, 4);
+	oobregion->length = mtd->oobsize - oobregion->offset;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops tegra_nand_oob_rs_ops = {
+	.ecc = tegra_nand_ooblayout_rs_ecc,
+	.free = tegra_nand_ooblayout_rs_free,
+};
+
+static int tegra_nand_ooblayout_bch_ecc(struct mtd_info *mtd, int section,
+				       struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_BCH * chip->ecc.strength, 8);
+
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = SKIP_SPARE_BYTES;
+	oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4);
+
+	return 0;
+}
+
+static int tegra_nand_ooblayout_bch_free(struct mtd_info *mtd, int section,
+					struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int bytes_per_step = (BITS_PER_STEP_BCH * chip->ecc.strength) / 8;
+
+	if (section > 0)
+		return -ERANGE;
+
+	oobregion->offset = SKIP_SPARE_BYTES +
+			    round_up(bytes_per_step * chip->ecc.steps, 4);
+	oobregion->length = mtd->oobsize - oobregion->offset;
+
+	return 0;
+}
+
+/*
+ * Layout with tag bytes is
+ *
+ * --------------------------------------------------------------------------
+ * | main area                       | skip bytes | tag bytes | parity | .. |
+ * --------------------------------------------------------------------------
+ *
+ * If not tag bytes are written, parity moves right after skip bytes!
+ */
+static const struct mtd_ooblayout_ops tegra_nand_oob_bch_ops = {
+	.ecc = tegra_nand_ooblayout_bch_ecc,
+	.free = tegra_nand_ooblayout_bch_free,
+};
+
+static irqreturn_t tegra_nand_irq(int irq, void *data)
+{
+	struct tegra_nand_controller *ctrl = data;
+	u32 isr, dma;
+
+	isr = readl_relaxed(ctrl->regs + ISR);
+	dma = readl_relaxed(ctrl->regs + DMA_CTRL);
+	dev_dbg(ctrl->dev, "isr %08x\n", isr);
+
+	if (!isr && !(dma & DMA_CTRL_IS_DONE))
+		return IRQ_NONE;
+
+	if (isr & ISR_CORRFAIL_ERR)
+		ctrl->last_read_error = true;
+
+	if (isr & ISR_CMD_DONE)
+		complete(&ctrl->command_complete);
+
+	if (isr & ISR_UND)
+		dev_dbg(ctrl->dev, "FIFO underrun\n");
+
+	if (isr & ISR_OVR)
+		dev_dbg(ctrl->dev, "FIFO overrun\n");
+
+	/* handle DMA interrupts */
+	if (dma & DMA_CTRL_IS_DONE) {
+		writel(dma, ctrl->regs + DMA_CTRL);
+		complete(&ctrl->dma_complete);
+	}
+
+	/* clear interrupts */
+	writel(isr, ctrl->regs + ISR);
+
+	return IRQ_HANDLED;
+}
+
+static int tegra_nand_cmd(struct nand_chip *chip,
+			 const struct nand_subop *subop)
+{
+	const struct nand_op_instr *instr;
+	const struct nand_op_instr *instr_data_in = NULL;
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	unsigned int op_id = -1, trfr_in_sz = 0, trfr_out_sz = 0, offset = 0;
+	bool first_cmd = true;
+	u32 cmd = 0;
+	u32 value;
+
+	for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+		unsigned int naddrs, i;
+		const u8 *addrs;
+		u32 addr1 = 0, addr2 = 0;
+
+		instr = &subop->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			if (first_cmd) {
+				cmd |= CMD_CLE;
+				writel(instr->ctx.cmd.opcode, ctrl->regs + CMD_1);
+			} else {
+				cmd |= CMD_SEC_CMD;
+				writel(instr->ctx.cmd.opcode, ctrl->regs + CMD_2);
+			}
+			first_cmd = false;
+			break;
+		case NAND_OP_ADDR_INSTR:
+			offset = nand_subop_get_addr_start_off(subop, op_id);
+			naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
+			addrs = &instr->ctx.addr.addrs[offset];
+
+			cmd |= CMD_ALE | CMD_ALE_SIZE(naddrs);
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				addr1 |= *addrs++ << (8 * i);
+			naddrs -= i;
+			for (i = 0; i < min_t(unsigned int, 4, naddrs); i++)
+				addr2 |= *addrs++ << (8 * i);
+			writel(addr1, ctrl->regs + ADDR_1);
+			writel(addr2, ctrl->regs + ADDR_2);
+			break;
+
+		case NAND_OP_DATA_IN_INSTR:
+			trfr_in_sz = nand_subop_get_data_len(subop, op_id);
+			offset = nand_subop_get_data_start_off(subop, op_id);
+
+			cmd |= CMD_TRANS_SIZE(trfr_in_sz) | CMD_PIO | CMD_RX | CMD_A_VALID;
+
+			instr_data_in = instr;
+			break;
+
+		case NAND_OP_DATA_OUT_INSTR:
+			trfr_out_sz = nand_subop_get_data_len(subop, op_id);
+			offset = nand_subop_get_data_start_off(subop, op_id);
+			trfr_out_sz = min_t(size_t, trfr_out_sz, 4);
+
+			cmd |= CMD_TRANS_SIZE(trfr_out_sz) | CMD_PIO | CMD_TX | CMD_A_VALID;
+
+			memcpy(&value, instr->ctx.data.buf.out + offset, trfr_out_sz);
+			writel(value, ctrl->regs + RESP);
+
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			cmd |= CMD_RBSY_CHK;
+			break;
+
+		}
+	}
+
+
+	cmd |= CMD_GO | CMD_CE(ctrl->cur_chip);
+	writel(cmd, ctrl->regs + CMD);
+	wait_for_completion(&ctrl->command_complete);
+
+	if (instr_data_in) {
+		u32 value;
+		size_t n = min_t(size_t, trfr_in_sz, 4);
+
+		value = readl(ctrl->regs + RESP);
+		memcpy(instr_data_in->ctx.data.buf.in + offset, &value, n);
+	}
+
+	return 0;
+}
+
+static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER(
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)),
+	NAND_OP_PARSER_PATTERN(tegra_nand_cmd,
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)),
+	);
+
+static int tegra_nand_exec_op(struct nand_chip *chip,
+			     const struct nand_operation *op,
+			     bool check_only)
+{
+	return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op,
+				      check_only);
+}
+static void tegra_nand_select_chip(struct mtd_info *mtd, int chip_nr)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+
+	ctrl->cur_chip = chip_nr;
+}
+
+static u32 tegra_nand_fill_address(struct tegra_nand_controller *ctrl,
+				   struct nand_chip *chip, int page)
+{
+	/* Lower 16-bits are column, always 0 */
+	writel(page << 16, ctrl->regs + ADDR_1);
+
+	if (chip->options & NAND_ROW_ADDR_3) {
+		writel(page >> 16, ctrl->regs + ADDR_2);
+		return 5;
+	}
+
+	return 4;
+}
+
+static void tegra_nand_hw_ecc(struct tegra_nand_controller *ctrl,
+			      struct nand_chip *chip, bool enable)
+{
+	u32 value;
+
+	switch (chip->ecc.algo) {
+	case NAND_ECC_RS:
+		value = readl(ctrl->regs + CFG);
+		if (enable)
+			value |= CFG_HW_ECC | CFG_ERR_COR;
+		else
+			value &= ~(CFG_HW_ECC | CFG_ERR_COR);
+		writel(value, ctrl->regs + CFG);
+		break;
+	case NAND_ECC_BCH:
+		value = readl(ctrl->regs + BCH_CONFIG);
+		if (enable)
+			value |= BCH_ENABLE;
+		else
+			value &= ~BCH_ENABLE;
+		writel(value, ctrl->regs + BCH_CONFIG);
+		break;
+	default:
+		dev_err(ctrl->dev, "Unsupported hardware ECC algorithm\n");
+		break;
+	}
+}
+
+static int tegra_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+				uint8_t *buf, int oob_required, int page)
+{
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	dma_addr_t dma_addr;
+	u32 value, addrs;
+	int ret, dma_len;
+
+	writel(NAND_CMD_READ0, ctrl->regs + CMD_1);
+	writel(NAND_CMD_READSTART, ctrl->regs + CMD_2);
+
+	addrs = tegra_nand_fill_address(ctrl, chip, page);
+
+	dma_len = mtd->writesize + (oob_required ? mtd->oobsize : 0);
+	dma_addr = dma_map_single(ctrl->dev, buf, dma_len, DMA_FROM_DEVICE);
+	ret = dma_mapping_error(ctrl->dev, dma_addr);
+	if (ret) {
+		dev_err(ctrl->dev, "dma mapping error\n");
+		return -EINVAL;
+	}
+
+	writel(mtd->writesize - 1, ctrl->regs + DMA_CFG_A);
+	writel(dma_addr, ctrl->regs + DATA_PTR);
+
+	if (oob_required) {
+		struct mtd_oob_region oobregion;
+		dma_addr_t dma_addr_oob = dma_addr + mtd->writesize;
+
+		mtd_ooblayout_free(mtd, 0, &oobregion);
+
+		writel(oobregion.length - 1, ctrl->regs + DMA_CFG_B);
+		writel(dma_addr_oob + oobregion.offset, ctrl->regs + TAG_PTR);
+	} else {
+		writel(0, ctrl->regs + DMA_CFG_B);
+		writel(0, ctrl->regs + TAG_PTR);
+	}
+
+	value = DMA_CTRL_GO | DMA_CTRL_IN | DMA_CTRL_PERF_EN |
+		DMA_CTRL_REUSE | DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
+		DMA_CTRL_BURST_16 | DMA_CTRL_EN_A;
+	if (oob_required)
+		value |= DMA_CTRL_EN_B;
+	writel(value, ctrl->regs + DMA_CTRL);
+
+	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
+		CMD_RBSY_CHK | CMD_GO | CMD_RX | CMD_TRANS_SIZE(9) |
+		CMD_A_VALID | CMD_CE(ctrl->cur_chip);
+	if (oob_required)
+		value |= CMD_B_VALID;
+	writel(value, ctrl->regs + CMD);
+
+	wait_for_completion(&ctrl->command_complete);
+	wait_for_completion(&ctrl->dma_complete);
+
+	dma_unmap_single(ctrl->dev, dma_addr, dma_len, DMA_FROM_DEVICE);
+
+	return 0;
+}
+
+static int tegra_nand_read_page_hwecc(struct mtd_info *mtd,
+				      struct nand_chip *chip,
+				      uint8_t *buf, int oob_required, int page)
+{
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	u32 value;
+	int ret;
+
+	tegra_nand_hw_ecc(ctrl, chip, true);
+	ret = tegra_nand_read_page(mtd, chip, buf, oob_required, page);
+	tegra_nand_hw_ecc(ctrl, chip, false);
+	if (ret)
+		return ret;
+
+	/* If no correctable or un-correctable errors occured we can return 0 */
+	if (!ctrl->last_read_error)
+		return 0;
+
+	/*
+	 * Correctable or un-correctable errors did occure. NAND dec status
+	 * contains information for all ECC selections
+	 */
+	ctrl->last_read_error = false;
+	value = readl(ctrl->regs + DEC_STAT_BUF);
+
+	if (value & DEC_STAT_BUF_FAIL_SEC_FLAG_MASK) {
+		/*
+		 * The ECC isn't smart enough to figure out if a page is
+		 * completely erased and flags an error in this case. So we
+		 * check the read data here to figure out if it's a legitimate
+		 * error or a false positive.
+		 */
+		int i, ret;
+		int flips_threshold = chip->ecc.strength / 2;
+		int max_bitflips = 0;
+
+		for (i = 0; i < chip->ecc.steps; i++) {
+			u8 *data = buf + (chip->ecc.size * i);
+
+			ret = nand_check_erased_ecc_chunk(data, chip->ecc.size,
+							  NULL, 0,
+							  NULL, 0,
+							  flips_threshold);
+			if (ret < 0)
+				mtd->ecc_stats.failed++;
+			else
+				max_bitflips = max(ret, max_bitflips);
+		}
+
+		return max_bitflips;
+	} else {
+		int max_corr_cnt, corr_sec_flag;
+
+		corr_sec_flag = (value & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >>
+				DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT;
+		max_corr_cnt = (value & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >>
+			       DEC_STAT_BUF_MAX_CORR_CNT_SHIFT;
+
+		/*
+		 * The value returned in the register is the maximum of
+		 * bitflips encountered in any of the ECC regions. As there is
+		 * no way to get the number of bitflips in a specific regions
+		 * we are not able to deliver correct stats but instead
+		 * overestimate the number of corrected bitflips by assuming
+		 * that all regions where errors have been corrected
+		 * encountered the maximum number of bitflips.
+		 */
+		mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag);
+
+		return max_corr_cnt;
+	}
+
+}
+
+static int tegra_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+				 const uint8_t *buf, int oob_required, int page)
+{
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	dma_addr_t dma_addr;
+	u32 value, addrs;
+	int ret, dma_len;
+
+	writel(NAND_CMD_SEQIN, ctrl->regs + CMD_1);
+	writel(NAND_CMD_PAGEPROG, ctrl->regs + CMD_2);
+
+	addrs = tegra_nand_fill_address(ctrl, chip, page);
+
+	dma_len = mtd->writesize + (oob_required ? mtd->oobsize : 0);
+	dma_addr = dma_map_single(ctrl->dev, (void *)buf, dma_len, DMA_TO_DEVICE);
+	ret = dma_mapping_error(ctrl->dev, dma_addr);
+	if (ret) {
+		dev_err(ctrl->dev, "dma mapping error\n");
+		return -EINVAL;
+	}
+
+	writel(mtd->writesize - 1, ctrl->regs + DMA_CFG_A);
+	writel(dma_addr, ctrl->regs + DATA_PTR);
+
+	if (oob_required) {
+		struct mtd_oob_region oobregion;
+		dma_addr_t dma_addr_oob = dma_addr + mtd->writesize;
+
+		mtd_ooblayout_free(mtd, 0, &oobregion);
+
+		writel(oobregion.length - 1, ctrl->regs + DMA_CFG_B);
+		writel(dma_addr_oob + oobregion.offset, ctrl->regs + TAG_PTR);
+	} else {
+		writel(0, ctrl->regs + DMA_CFG_B);
+		writel(0, ctrl->regs + TAG_PTR);
+	}
+
+	value = DMA_CTRL_GO | DMA_CTRL_OUT | DMA_CTRL_PERF_EN |
+		DMA_CTRL_IE_DONE | DMA_CTRL_IS_DONE |
+		DMA_CTRL_BURST_16 | DMA_CTRL_EN_A;
+	if (oob_required)
+		value |= DMA_CTRL_EN_B;
+	writel(value, ctrl->regs + DMA_CTRL);
+
+	value = CMD_CLE | CMD_ALE | CMD_ALE_SIZE(addrs) | CMD_SEC_CMD |
+		CMD_AFT_DAT | CMD_RBSY_CHK | CMD_GO | CMD_TX | CMD_A_VALID |
+		CMD_TRANS_SIZE(9) | CMD_CE(ctrl->cur_chip);
+	if (oob_required)
+		value |= CMD_B_VALID;
+	writel(value, ctrl->regs + CMD);
+
+	wait_for_completion(&ctrl->command_complete);
+	wait_for_completion(&ctrl->dma_complete);
+
+
+	dma_unmap_single(ctrl->dev, dma_addr, dma_len, DMA_TO_DEVICE);
+
+	return 0;
+}
+
+static int tegra_nand_write_page_hwecc(struct mtd_info *mtd,
+				       struct nand_chip *chip,
+				       const uint8_t *buf, int oob_required,
+				       int page)
+{
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	int ret;
+
+	tegra_nand_hw_ecc(ctrl, chip, true);
+	ret = tegra_nand_write_page(mtd, chip, buf, oob_required, page);
+	tegra_nand_hw_ecc(ctrl, chip, false);
+
+	return ret;
+}
+
+static void tegra_nand_setup_timing(struct tegra_nand_controller *ctrl,
+				    const struct nand_sdr_timings *timings)
+{
+	/*
+	 * The period (and all other timings in this function) is in ps,
+	 * so need to take care here to avoid integer overflows.
+	 */
+	unsigned int rate = clk_get_rate(ctrl->clk) / 1000000;
+	unsigned int period = DIV_ROUND_UP(1000000, rate);
+	u32 val, reg = 0;
+
+	val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min,
+				timings->tRC_min), period);
+	if (val > 2)
+		val -= 3;
+	reg |= TIMING_TCR_TAR_TRR(val);
+
+	val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min),
+				   max(timings->tALS_min, timings->tALH_min)),
+			   period);
+	if (val > 1)
+		val -= 2;
+	reg |= TIMING_TCS(val);
+
+	val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000,
+			   period);
+	reg |= TIMING_TRP(val) | TIMING_TRP_RESP(val);
+
+	reg |= TIMING_TWB(DIV_ROUND_UP(timings->tWB_max, period));
+	reg |= TIMING_TWHR(DIV_ROUND_UP(timings->tWHR_min, period));
+	reg |= TIMING_TWH(DIV_ROUND_UP(timings->tWH_min, period));
+	reg |= TIMING_TWP(DIV_ROUND_UP(timings->tWP_min, period));
+	reg |= TIMING_TRH(DIV_ROUND_UP(timings->tRHW_min, period));
+
+	writel(reg, ctrl->regs + TIMING_1);
+
+	val = DIV_ROUND_UP(timings->tADL_min, period);
+	if (val > 2)
+		val -= 3;
+	reg = TIMING_TADL(val);
+
+	writel(reg, ctrl->regs + TIMING_2);
+}
+
+static int tegra_nand_setup_data_interface(struct mtd_info *mtd, int csline,
+					   const struct nand_data_interface *conf)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller);
+	const struct nand_sdr_timings *timings;
+
+	timings = nand_get_sdr_timings(conf);
+	if (IS_ERR(timings))
+		return PTR_ERR(timings);
+
+	if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+		return 0;
+
+	tegra_nand_setup_timing(ctrl, timings);
+
+	return 0;
+}
+
+static int tegra_nand_chips_init(struct device *dev,
+				 struct tegra_nand_controller *ctrl)
+{
+	struct device_node *np = dev->of_node;
+	struct device_node *np_nand;
+	int nchips = of_get_child_count(np);
+	struct tegra_nand_chip *nand;
+	struct mtd_info *mtd;
+	struct nand_chip *chip;
+	unsigned long config, bch_config = 0;
+	int bits_per_step;
+	int err;
+
+	if (nchips != 1) {
+		dev_err(dev, "currently only one NAND chip supported\n");
+		return -EINVAL;
+	}
+
+	np_nand = of_get_next_child(np, NULL);
+
+	nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
+	if (!nand) {
+		dev_err(dev, "could not allocate chip structure\n");
+		return -ENOMEM;
+	}
+
+	nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW);
+
+	if (IS_ERR(nand->wp_gpio)) {
+		err = PTR_ERR(nand->wp_gpio);
+		dev_err(dev, "failed to request WP GPIO: %d\n", err);
+		return err;
+	}
+
+	chip = &nand->chip;
+	chip->controller = &ctrl->controller;
+	ctrl->chip = chip;
+
+	mtd = nand_to_mtd(chip);
+
+	mtd->dev.parent = dev;
+	mtd->name = "tegra_nand";
+	mtd->owner = THIS_MODULE;
+
+	nand_set_flash_node(chip, np_nand);
+
+	chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER;
+	chip->exec_op = tegra_nand_exec_op;
+	chip->select_chip = tegra_nand_select_chip;
+	chip->setup_data_interface = tegra_nand_setup_data_interface;
+
+	err = nand_scan_ident(mtd, 1, NULL);
+	if (err)
+		return err;
+
+	if (chip->bbt_options & NAND_BBT_USE_FLASH)
+		chip->bbt_options |= NAND_BBT_NO_OOB;
+
+	chip->ecc.mode = NAND_ECC_HW;
+	if (!chip->ecc.size)
+		chip->ecc.size = 512;
+	if (chip->ecc.size != 512)
+		return -EINVAL;
+
+	chip->ecc.read_page = tegra_nand_read_page_hwecc;
+	chip->ecc.write_page = tegra_nand_write_page_hwecc;
+	/* Not functional for unknown reason...
+	chip->ecc.read_page_raw = tegra_nand_read_page;
+	chip->ecc.write_page_raw = tegra_nand_write_page;
+	*/
+	config = readl(ctrl->regs + CFG);
+	config |= CFG_PIPE_EN | CFG_SKIP_SPARE | CFG_SKIP_SPARE_SIZE_4;
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		config |= CFG_BUS_WIDTH_16;
+
+	switch (chip->ecc.algo) {
+	case NAND_ECC_RS:
+		bits_per_step = BITS_PER_STEP_RS * chip->ecc.strength;
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_rs_ops);
+		switch (chip->ecc.strength) {
+		case 4:
+			config |= CFG_ECC_SEL | CFG_TVAL_4;
+			break;
+		case 6:
+			config |= CFG_ECC_SEL | CFG_TVAL_6;
+			break;
+		case 8:
+			config |= CFG_ECC_SEL | CFG_TVAL_8;
+			break;
+		default:
+			dev_err(dev, "ECC strength %d not supported\n",
+				chip->ecc.strength);
+			return -EINVAL;
+		}
+		break;
+	case NAND_ECC_BCH:
+		bits_per_step = BITS_PER_STEP_BCH * chip->ecc.strength;
+		mtd_set_ooblayout(mtd, &tegra_nand_oob_bch_ops);
+		switch (chip->ecc.strength) {
+		case 4:
+			bch_config = BCH_TVAL_4;
+			break;
+		case 8:
+			bch_config = BCH_TVAL_8;
+			break;
+		case 14:
+			bch_config = BCH_TVAL_14;
+			break;
+		case 16:
+			bch_config = BCH_TVAL_16;
+			break;
+		default:
+			dev_err(dev, "ECC strength %d not supported\n",
+				chip->ecc.strength);
+			return -EINVAL;
+		}
+		break;
+	default:
+		dev_err(dev, "ECC algorithm not supported\n");
+		return -EINVAL;
+	}
+
+	chip->ecc.bytes = DIV_ROUND_UP(bits_per_step, 8);
+
+	switch (mtd->writesize) {
+	case 256:
+		config |= CFG_PS_256;
+		break;
+	case 512:
+		config |= CFG_PS_512;
+		break;
+	case 1024:
+		config |= CFG_PS_1024;
+		break;
+	case 2048:
+		config |= CFG_PS_2048;
+		break;
+	case 4096:
+		config |= CFG_PS_4096;
+		break;
+	default:
+		dev_err(dev, "unhandled writesize %d\n", mtd->writesize);
+		return -ENODEV;
+	}
+
+	writel(config, ctrl->regs + CFG);
+	writel(bch_config, ctrl->regs + BCH_CONFIG);
+
+	err = nand_scan_tail(mtd);
+	if (err)
+		return err;
+
+	config |= CFG_TAG_BYTE_SIZE(mtd_ooblayout_count_freebytes(mtd) - 1);
+	writel(config, ctrl->regs + CFG);
+
+	err = mtd_device_register(mtd, NULL, 0);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int tegra_nand_probe(struct platform_device *pdev)
+{
+	struct reset_control *rst;
+	struct tegra_nand_controller *ctrl;
+	struct resource *res;
+	unsigned long value;
+	int irq, err = 0;
+
+	ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->dev = &pdev->dev;
+	nand_hw_control_init(&ctrl->controller);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	ctrl->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(ctrl->regs))
+		return PTR_ERR(ctrl->regs);
+
+	rst = devm_reset_control_get(&pdev->dev, "nand");
+	if (IS_ERR(rst))
+		return PTR_ERR(rst);
+
+	ctrl->clk = devm_clk_get(&pdev->dev, "nand");
+	if (IS_ERR(ctrl->clk))
+		return PTR_ERR(ctrl->clk);
+
+	err = clk_prepare_enable(ctrl->clk);
+	if (err)
+		return err;
+
+	reset_control_reset(rst);
+
+	value = HWSTATUS_RDSTATUS_MASK(1) | HWSTATUS_RDSTATUS_VALUE(0) |
+		HWSTATUS_RBSY_MASK(NAND_STATUS_READY) |
+		HWSTATUS_RBSY_VALUE(NAND_STATUS_READY);
+	writel(NAND_CMD_STATUS, ctrl->regs + HWSTATUS_CMD);
+	writel(value, ctrl->regs + HWSTATUS_MASK);
+
+	init_completion(&ctrl->command_complete);
+	init_completion(&ctrl->dma_complete);
+
+	/* clear interrupts */
+	value = readl(ctrl->regs + ISR);
+	writel(value, ctrl->regs + ISR);
+
+	irq = platform_get_irq(pdev, 0);
+	err = devm_request_irq(&pdev->dev, irq, tegra_nand_irq, 0,
+			       dev_name(&pdev->dev), ctrl);
+	if (err)
+		goto err_disable_clk;
+
+	writel(DMA_CTRL_IS_DONE, ctrl->regs + DMA_CTRL);
+
+	/* enable interrupts */
+	value = IER_UND | IER_OVR | IER_CMD_DONE | IER_ECC_ERR | IER_GIE;
+	writel(value, ctrl->regs + IER);
+
+	/* reset config */
+	writel(0, ctrl->regs + CFG);
+
+	err = tegra_nand_chips_init(ctrl->dev, ctrl);
+	if (err)
+		goto err_disable_clk;
+
+	platform_set_drvdata(pdev, ctrl);
+
+	return 0;
+
+err_disable_clk:
+	clk_disable_unprepare(ctrl->clk);
+	return err;
+}
+
+static int tegra_nand_remove(struct platform_device *pdev)
+{
+	struct tegra_nand_controller *ctrl = platform_get_drvdata(pdev);
+
+	nand_release(nand_to_mtd(ctrl->chip));
+
+	clk_disable_unprepare(ctrl->clk);
+
+	return 0;
+}
+
+static const struct of_device_id tegra_nand_of_match[] = {
+	{ .compatible = "nvidia,tegra20-nand" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver tegra_nand_driver = {
+	.driver = {
+		.name = "tegra-nand",
+		.of_match_table = tegra_nand_of_match,
+	},
+	.probe = tegra_nand_probe,
+	.remove = tegra_nand_remove,
+};
+module_platform_driver(tegra_nand_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra NAND driver");
+MODULE_AUTHOR("Thierry Reding <thierry.reding@nvidia.com>");
+MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
+MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(of, tegra_nand_of_match);
-- 
2.17.0

^ permalink raw reply related

* [PATCH v2 2/6] mtd: rawnand: tegra: add devicetree binding
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

From: Lucas Stach <dev@lynxeye.de>

This adds the devicetree binding for the Tegra 2 NAND flash
controller.

Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 .../bindings/mtd/nvidia,tegra20-nand.txt      | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt

diff --git a/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
new file mode 100644
index 000000000000..49e472af1b39
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/nvidia,tegra20-nand.txt
@@ -0,0 +1,62 @@
+NVIDIA Tegra NAND Flash controller
+
+Required properties:
+- compatible: Must be one of:
+  - "nvidia,tegra20-nand"
+- reg: MMIO address range
+- interrupts: interrupt output of the NFC controller
+- clocks: Must contain an entry for each entry in clock-names.
+  See ../clocks/clock-bindings.txt for details.
+- clock-names: Must include the following entries:
+  - nand
+- resets: Must contain an entry for each entry in reset-names.
+  See ../reset/reset.txt for details.
+- reset-names: Must include the following entries:
+  - nand
+
+Optional children nodes:
+Individual NAND chips are children of the NAND controller node. Currently
+only one NAND chip supported.
+
+Required children node properties:
+- reg: An integer ranging from 1 to 6 representing the CS line to use.
+
+Optional children node properties:
+- nand-ecc-mode: String, operation mode of the NAND ecc mode. "hw" by default
+- nand-ecc-algo: string, algorithm of NAND ECC.
+		 Supported values are: "rs", "bch".
+- nand-bus-width : 8 or 16 bus width if not present 8
+- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
+- nand-ecc-strength: integer representing the number of bits to correct
+		     per ECC step. Supported strength using HW ECC modes are:
+		     - RS: 4, 6, 8
+		     - BCH: 4, 8, 14, 16
+- nand-ecc-step-size: integer representing the number of data bytes
+		      that are covered by a single ECC step. Must be 512.
+- wp-gpios: GPIO specifier for the write protect pin.
+
+Optional child node of NAND chip nodes:
+Partitions: see partition.txt
+
+  Example:
+	nand@70008000 {
+		compatible = "nvidia,tegra20-nand";
+		reg = <0x70008000 0x100>;
+		interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&tegra_car TEGRA20_CLK_NDFLASH>;
+		clock-names = "nand";
+		resets = <&tegra_car 13>;
+		reset-names = "nand";
+
+		nand-chip@0 {
+			reg = <0>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			nand-bus-width = <8>;
+			nand-on-flash-bbt;
+			nand-ecc-algo = "bch";
+			nand-ecc-step-size = <512>;
+			nand-ecc-strength = <8>;
+			wp-gpios = <&gpio TEGRA_GPIO(S, 0) GPIO_ACTIVE_LOW>;
+		};
+	};
-- 
2.17.0

^ permalink raw reply related

* [PATCH v2 1/6] mtd: rawnand: add Reed-Solomon error correction algorithm
From: Stefan Agner @ 2018-05-27 21:54 UTC (permalink / raw)
  To: boris.brezillon, dwmw2, computersforpeace, marek.vasut, robh+dt,
	mark.rutland, thierry.reding, mturquette, sboyd
  Cc: dev, miquel.raynal, richard, marcel, krzk, digetx,
	benjamin.lindqvist, jonathanh, pdeschrijver, pgaikwad, mirza.krak,
	linux-mtd, linux-tegra, devicetree, linux-kernel, linux-clk,
	Stefan Agner

Add Reed-Solomon (RS) to the enumeration of ECC algorithms.

Signed-off-by: Stefan Agner <stefan@agner.ch>
---
 drivers/mtd/nand/raw/nand_base.c | 1 +
 include/linux/mtd/rawnand.h      | 1 +
 2 files changed, 2 insertions(+)

diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index f28c3a555861..9eb5678dd6d0 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -5744,6 +5744,7 @@ static int of_get_nand_ecc_mode(struct device_node *np)
 static const char * const nand_ecc_algos[] = {
 	[NAND_ECC_HAMMING]	= "hamming",
 	[NAND_ECC_BCH]		= "bch",
+	[NAND_ECC_RS]		= "rs",
 };
 
 static int of_get_nand_ecc_algo(struct device_node *np)
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 5dad59b31244..6a82da8c44ce 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -114,6 +114,7 @@ enum nand_ecc_algo {
 	NAND_ECC_UNKNOWN,
 	NAND_ECC_HAMMING,
 	NAND_ECC_BCH,
+	NAND_ECC_RS,
 };
 
 /*
-- 
2.17.0

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox