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
This commit is contained in:
Florian Fainelli 2009-05-25 13:13:10 +00:00
parent 04bca7b528
commit 3b92b4de00

View file

@ -40,6 +40,8 @@
#define VLYNQ_CTRL_INT2CFG 0x00000080
#define VLYNQ_CTRL_RESET 0x00000001
#define VLYNQ_CTRL_CLOCK_MASK (0x7 << 16)
#define VLYNQ_INT_OFFSET 0x00000014
#define VLYNQ_REMOTE_OFFSET 0x00000080
@ -114,6 +116,24 @@ int vlynq_linked(struct vlynq_device *dev)
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)
{
u32 val;
@ -357,9 +377,100 @@ void vlynq_unregister_driver(struct vlynq_driver *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)
{
int i, result;
int result;
struct plat_vlynq_ops *ops = dev->dev.platform_data;
result = ops->on(dev);
@ -369,30 +480,23 @@ static int __vlynq_enable_device(struct vlynq_device *dev)
switch (dev->divisor) {
case vlynq_div_external:
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);
/* 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;
}
/* 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;
return 0;
}
}
break;
case vlynq_ldiv1: case vlynq_ldiv2: case vlynq_ldiv3: case vlynq_ldiv4:
case vlynq_ldiv5: case vlynq_ldiv6: case vlynq_ldiv7: case vlynq_ldiv8:
vlynq_reg_write(dev->local->control,