Browse Source

I recently came across an ar7 device which has the vlynq hardwired so that the clocks are always generated by the remote device instead of the local one.

Upon initialization the current version of vlynq driver disables
remote clock generation and causes the entire bus to hang on my
device.

This patch adds support for detecting which device (local or remote)
is responsible of clock generation and implements clock
initialization based on detection result.

Signed-off-by: Antti Seppala <a.seppala at gmail.com>

SVN-Revision: 16049
Florian Fainelli 16 years ago
parent
commit
3b92b4de00
1 changed files with 127 additions and 23 deletions
  1. 127 23
      target/linux/ar7/files/drivers/vlynq/vlynq.c

+ 127 - 23
target/linux/ar7/files/drivers/vlynq/vlynq.c

@@ -40,6 +40,8 @@
 #define VLYNQ_CTRL_INT2CFG		0x00000080
 #define VLYNQ_CTRL_INT2CFG		0x00000080
 #define VLYNQ_CTRL_RESET		0x00000001
 #define VLYNQ_CTRL_RESET		0x00000001
 
 
+#define VLYNQ_CTRL_CLOCK_MASK          (0x7 << 16)
+
 #define VLYNQ_INT_OFFSET		0x00000014
 #define VLYNQ_INT_OFFSET		0x00000014
 #define VLYNQ_REMOTE_OFFSET		0x00000080
 #define VLYNQ_REMOTE_OFFSET		0x00000080
 
 
@@ -114,6 +116,24 @@ int vlynq_linked(struct vlynq_device *dev)
 	return 0;
 	return 0;
 }
 }
 
 
+static void vlynq_reset(struct vlynq_device *dev)
+{
+	vlynq_reg_write(dev->local->control,
+			vlynq_reg_read(dev->local->control) |
+			VLYNQ_CTRL_RESET);
+
+	/* Wait for the devices to finish resetting */
+	msleep(5);
+
+	/* Remove reset bit */
+	vlynq_reg_write(dev->local->control,
+			vlynq_reg_read(dev->local->control) &
+			~VLYNQ_CTRL_RESET);
+
+	/* Give some time for the devices to settle */
+	msleep(5);
+}
+
 static void vlynq_irq_unmask(unsigned int irq)
 static void vlynq_irq_unmask(unsigned int irq)
 {
 {
 	u32 val;
 	u32 val;
@@ -357,9 +377,100 @@ void vlynq_unregister_driver(struct vlynq_driver *driver)
 }
 }
 EXPORT_SYMBOL(vlynq_unregister_driver);
 EXPORT_SYMBOL(vlynq_unregister_driver);
 
 
+static int __vlynq_try_remote(struct vlynq_device *dev)
+{
+	int i;
+
+	vlynq_reset(dev);
+	for (i = dev->dev_id ? vlynq_rdiv2 : vlynq_rdiv8; dev->dev_id ?
+			i <= vlynq_rdiv8 : i >= vlynq_rdiv2;
+		dev->dev_id ? i++ : i--) {
+
+		if (!vlynq_linked(dev))
+			break;
+
+		vlynq_reg_write(dev->remote->control,
+				(vlynq_reg_read(dev->remote->control) &
+				~VLYNQ_CTRL_CLOCK_MASK) |
+				VLYNQ_CTRL_CLOCK_INT |
+				VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1));
+		vlynq_reg_write(dev->local->control,
+				((vlynq_reg_read(dev->local->control)
+				& ~(VLYNQ_CTRL_CLOCK_INT |
+				VLYNQ_CTRL_CLOCK_MASK)) |
+				VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1)));
+
+		if (vlynq_linked(dev)) {
+			printk(KERN_DEBUG
+				"%s: using remote clock divisor %d\n",
+				dev->dev.bus_id, i - vlynq_rdiv1 + 1);
+			dev->divisor = i;
+			return 0;
+		} else {
+			vlynq_reset(dev);
+		}
+	}
+
+	return -ENODEV;
+}
+
+static int __vlynq_try_local(struct vlynq_device *dev)
+{
+	int i;
+	
+	vlynq_reset(dev);
+
+	for (i = dev->dev_id ? vlynq_ldiv2 : vlynq_ldiv8; dev->dev_id ?
+			i <= vlynq_ldiv8 : i >= vlynq_ldiv2;
+		dev->dev_id ? i++ : i--) {
+
+		vlynq_reg_write(dev->local->control,
+				(vlynq_reg_read(dev->local->control) &
+				~VLYNQ_CTRL_CLOCK_MASK) |
+				VLYNQ_CTRL_CLOCK_INT |
+				VLYNQ_CTRL_CLOCK_DIV(i - vlynq_ldiv1));
+
+		if (vlynq_linked(dev)) {
+			printk(KERN_DEBUG
+				"%s: using local clock divisor %d\n",
+				dev->dev.bus_id, i - vlynq_ldiv1 + 1);
+			dev->divisor = i;
+			return 0;
+		} else {
+			vlynq_reset(dev);
+		}
+	}
+
+	return -ENODEV;
+}
+
+static int __vlynq_try_external(struct vlynq_device *dev)
+{
+	vlynq_reset(dev);
+	if (!vlynq_linked(dev))
+		return -ENODEV;
+
+	vlynq_reg_write(dev->remote->control,
+			(vlynq_reg_read(dev->remote->control) &
+			~VLYNQ_CTRL_CLOCK_INT));
+
+	vlynq_reg_write(dev->local->control,
+			(vlynq_reg_read(dev->local->control) &
+			~VLYNQ_CTRL_CLOCK_INT));
+
+	if (vlynq_linked(dev)) {
+		printk(KERN_DEBUG "%s: using external clock\n",
+			dev->dev.bus_id);
+			dev->divisor = vlynq_div_external;
+		return 0;
+	}
+	
+	return -ENODEV;
+}
+
 static int __vlynq_enable_device(struct vlynq_device *dev)
 static int __vlynq_enable_device(struct vlynq_device *dev)
 {
 {
-	int i, result;
+	int result;
 	struct plat_vlynq_ops *ops = dev->dev.platform_data;
 	struct plat_vlynq_ops *ops = dev->dev.platform_data;
 
 
 	result = ops->on(dev);
 	result = ops->on(dev);
@@ -369,30 +480,23 @@ static int __vlynq_enable_device(struct vlynq_device *dev)
 	switch (dev->divisor) {
 	switch (dev->divisor) {
 	case vlynq_div_external:
 	case vlynq_div_external:
 	case vlynq_div_auto:
 	case vlynq_div_auto:
-		vlynq_reg_write(dev->local->control, 0);
-		vlynq_reg_write(dev->remote->control, 0);
-		if (vlynq_linked(dev)) {
-			dev->divisor = vlynq_div_external;
-			printk(KERN_DEBUG "%s: using external clock\n",
-				dev->dev.bus_id);
-			return 0;
-		}
-
-		/* Only try locally supplied clock, others cause problems */
-		for (i = dev->dev_id ? vlynq_ldiv2 : vlynq_ldiv8; dev->dev_id ?
-				i <= vlynq_ldiv8 : i >= vlynq_ldiv2;
-				dev->dev_id ? i++ : i--) {
-			vlynq_reg_write(dev->local->control,
-					VLYNQ_CTRL_CLOCK_INT |
-					VLYNQ_CTRL_CLOCK_DIV(i - vlynq_ldiv1));
-			if (vlynq_linked(dev)) {
-				printk(KERN_DEBUG
-				       "%s: using local clock divisor %d\n",
-				       dev->dev.bus_id, i - vlynq_ldiv1 + 1);
-				dev->divisor = i;
+		/* When the device is brought from reset it should have clock
+		generation negotiated by hardware.
+		Check which device is generating clocks and perform setup
+		accordingly */
+		if (vlynq_linked(dev) && vlynq_reg_read(dev->remote->control) &
+		   VLYNQ_CTRL_CLOCK_INT) {
+			if (!__vlynq_try_remote(dev) ||
+				!__vlynq_try_local(dev)  ||
+				!__vlynq_try_external(dev))
+				return 0;
+		} else {
+			if (!__vlynq_try_external(dev) ||
+				!__vlynq_try_local(dev)    ||
+				!__vlynq_try_remote(dev))
 				return 0;
 				return 0;
-			}
 		}
 		}
+		break;
 	case vlynq_ldiv1: case vlynq_ldiv2: case vlynq_ldiv3: case vlynq_ldiv4:
 	case vlynq_ldiv1: case vlynq_ldiv2: case vlynq_ldiv3: case vlynq_ldiv4:
 	case vlynq_ldiv5: case vlynq_ldiv6: case vlynq_ldiv7: case vlynq_ldiv8:
 	case vlynq_ldiv5: case vlynq_ldiv6: case vlynq_ldiv7: case vlynq_ldiv8:
 		vlynq_reg_write(dev->local->control,
 		vlynq_reg_write(dev->local->control,