/* * Realtek RTL8366 SMI interface driver * * Copyright (C) 2009-2010 Gabor Juhos * * 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. */ #include #include #include #include #include #include #include #include "rtl8366_smi.h" #define RTL8366_SMI_ACK_RETRY_COUNT 5 #define RTL8366_SMI_CLK_DELAY 10 /* nsec */ static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) { ndelay(RTL8366_SMI_CLK_DELAY); } static void rtl8366_smi_start(struct rtl8366_smi *smi) { unsigned int sda = smi->gpio_sda; unsigned int sck = smi->gpio_sck; /* * Set GPIO pins to output mode, with initial state: * SCK = 0, SDA = 1 */ gpio_direction_output(sck, 0); gpio_direction_output(sda, 1); rtl8366_smi_clk_delay(smi); /* CLK 1: 0 -> 1, 1 -> 0 */ gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 0); rtl8366_smi_clk_delay(smi); /* CLK 2: */ gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sda, 0); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 0); rtl8366_smi_clk_delay(smi); gpio_set_value(sda, 1); } static void rtl8366_smi_stop(struct rtl8366_smi *smi) { unsigned int sda = smi->gpio_sda; unsigned int sck = smi->gpio_sck; rtl8366_smi_clk_delay(smi); gpio_set_value(sda, 0); gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sda, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 0); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 1); /* add a click */ rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 0); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 1); /* set GPIO pins to input mode */ gpio_direction_input(sda); gpio_direction_input(sck); } static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) { unsigned int sda = smi->gpio_sda; unsigned int sck = smi->gpio_sck; for (; len > 0; len--) { rtl8366_smi_clk_delay(smi); /* prepare data */ gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); rtl8366_smi_clk_delay(smi); /* clocking */ gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); gpio_set_value(sck, 0); } } static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) { unsigned int sda = smi->gpio_sda; unsigned int sck = smi->gpio_sck; gpio_direction_input(sda); for (*data = 0; len > 0; len--) { u32 u; rtl8366_smi_clk_delay(smi); /* clocking */ gpio_set_value(sck, 1); rtl8366_smi_clk_delay(smi); u = !!gpio_get_value(sda); gpio_set_value(sck, 0); *data |= (u << (len - 1)); } gpio_direction_output(sda, 0); } static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) { int retry_cnt; retry_cnt = 0; do { u32 ack; rtl8366_smi_read_bits(smi, 1, &ack); if (ack == 0) break; if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) return -EIO; } while (1); return 0; } static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) { rtl8366_smi_write_bits(smi, data, 8); return rtl8366_smi_wait_for_ack(smi); } static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) { u32 t; /* read data */ rtl8366_smi_read_bits(smi, 8, &t); *data = (t & 0xff); /* send an ACK */ rtl8366_smi_write_bits(smi, 0x00, 1); return 0; } static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) { u32 t; /* read data */ rtl8366_smi_read_bits(smi, 8, &t); *data = (t & 0xff); /* send an ACK */ rtl8366_smi_write_bits(smi, 0x01, 1); return 0; } int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) { unsigned long flags; u8 lo = 0; u8 hi = 0; int ret; spin_lock_irqsave(&smi->lock, flags); rtl8366_smi_start(smi); /* send READ command */ ret = rtl8366_smi_write_byte(smi, 0x0a << 4 | 0x04 << 1 | 0x01); if (ret) goto out; /* set ADDR[7:0] */ ret = rtl8366_smi_write_byte(smi, addr & 0xff); if (ret) goto out; /* set ADDR[15:8] */ ret = rtl8366_smi_write_byte(smi, addr >> 8); if (ret) goto out; /* read DATA[7:0] */ rtl8366_smi_read_byte0(smi, &lo); /* read DATA[15:8] */ rtl8366_smi_read_byte1(smi, &hi); *data = ((u32) lo) | (((u32) hi) << 8); ret = 0; out: rtl8366_smi_stop(smi); spin_unlock_irqrestore(&smi->lock, flags); return ret; } EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) { unsigned long flags; int ret; spin_lock_irqsave(&smi->lock, flags); rtl8366_smi_start(smi); /* send WRITE command */ ret = rtl8366_smi_write_byte(smi, 0x0a << 4 | 0x04 << 1 | 0x00); if (ret) goto out; /* set ADDR[7:0] */ ret = rtl8366_smi_write_byte(smi, addr & 0xff); if (ret) goto out; /* set ADDR[15:8] */ ret = rtl8366_smi_write_byte(smi, addr >> 8); if (ret) goto out; /* write DATA[7:0] */ ret = rtl8366_smi_write_byte(smi, data & 0xff); if (ret) goto out; /* write DATA[15:8] */ ret = rtl8366_smi_write_byte(smi, data >> 8); if (ret) goto out; ret = 0; out: rtl8366_smi_stop(smi); spin_unlock_irqrestore(&smi->lock, flags); return ret; } EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) { u32 t; int err; err = rtl8366_smi_read_reg(smi, addr, &t); if (err) return err; err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); return err; } EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) { int err; int i; *used = 0; for (i = 0; i < smi->num_ports; i++) { int index = 0; err = smi->ops->get_mc_index(smi, i, &index); if (err) return err; if (mc_index == index) { *used = 1; break; } } return 0; } int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, u32 untag, u32 fid) { struct rtl8366_vlan_4k vlan4k; int err; int i; /* Update the 4K table */ err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); if (err) return err; vlan4k.member = member; vlan4k.untag = untag; vlan4k.fid = fid; err = smi->ops->set_vlan_4k(smi, &vlan4k); if (err) return err; /* Try to find an existing MC entry for this VID */ for (i = 0; i < smi->num_vlan_mc; i++) { struct rtl8366_vlan_mc vlanmc; err = smi->ops->get_vlan_mc(smi, i, &vlanmc); if (err) return err; if (vid == vlanmc.vid) { /* update the MC entry */ vlanmc.member = member; vlanmc.untag = untag; vlanmc.fid = fid; err = smi->ops->set_vlan_mc(smi, i, &vlanmc); break; } } return err; } EXPORT_SYMBOL_GPL(rtl8366_set_vlan); int rtl8366_reset_vlan(struct rtl8366_smi *smi) { struct rtl8366_vlan_mc vlanmc; int err; int i; /* clear VLAN member configurations */ vlanmc.vid = 0; vlanmc.priority = 0; vlanmc.member = 0; vlanmc.untag = 0; vlanmc.fid = 0; for (i = 0; i < smi->num_vlan_mc; i++) { err = smi->ops->set_vlan_mc(smi, i, &vlanmc); if (err) return err; } for (i = 0; i < smi->num_ports; i++) { if (i == smi->cpu_port) continue; err = rtl8366_set_vlan(smi, (i + 1), (1 << i) | (1 << smi->cpu_port), (1 << i) | (1 << smi->cpu_port), 0); if (err) return err; err = rtl8366_set_pvid(smi, i, (i + 1)); if (err) return err; } return 0; } EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) { struct rtl8366_vlan_mc vlanmc; int err; int index; err = smi->ops->get_mc_index(smi, port, &index); if (err) return err; err = smi->ops->get_vlan_mc(smi, index, &vlanmc); if (err) return err; *val = vlanmc.vid; return 0; } EXPORT_SYMBOL_GPL(rtl8366_get_pvid); int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, unsigned vid) { struct rtl8366_vlan_mc vlanmc; struct rtl8366_vlan_4k vlan4k; int err; int i; /* Try to find an existing MC entry for this VID */ for (i = 0; i < smi->num_vlan_mc; i++) { err = smi->ops->get_vlan_mc(smi, i, &vlanmc); if (err) return err; if (vid == vlanmc.vid) { err = smi->ops->set_vlan_mc(smi, i, &vlanmc); if (err) return err; err = smi->ops->set_mc_index(smi, port, i); return err; } } /* We have no MC entry for this VID, try to find an empty one */ for (i = 0; i < smi->num_vlan_mc; i++) { err = smi->ops->get_vlan_mc(smi, i, &vlanmc); if (err) return err; if (vlanmc.vid == 0 && vlanmc.member == 0) { /* Update the entry from the 4K table */ err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); if (err) return err; vlanmc.vid = vid; vlanmc.member = vlan4k.member; vlanmc.untag = vlan4k.untag; vlanmc.fid = vlan4k.fid; err = smi->ops->set_vlan_mc(smi, i, &vlanmc); if (err) return err; err = smi->ops->set_mc_index(smi, port, i); return err; } } /* MC table is full, try to find an unused entry and replace it */ for (i = 0; i < smi->num_vlan_mc; i++) { int used; err = rtl8366_mc_is_used(smi, i, &used); if (err) return err; if (!used) { /* Update the entry from the 4K table */ err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); if (err) return err; vlanmc.vid = vid; vlanmc.member = vlan4k.member; vlanmc.untag = vlan4k.untag; vlanmc.fid = vlan4k.fid; err = smi->ops->set_vlan_mc(smi, i, &vlanmc); if (err) return err; err = smi->ops->set_mc_index(smi, port, i); return err; } } dev_err(smi->parent, "all VLAN member configurations are in use\n"); return -ENOSPC; } EXPORT_SYMBOL_GPL(rtl8366_set_pvid); static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) { int ret; int i; smi->mii_bus = mdiobus_alloc(); if (smi->mii_bus == NULL) { ret = -ENOMEM; goto err; } smi->mii_bus->priv = (void *) smi; smi->mii_bus->name = dev_name(smi->parent); smi->mii_bus->read = smi->ops->mii_read; smi->mii_bus->write = smi->ops->mii_write; snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(smi->parent)); smi->mii_bus->parent = smi->parent; smi->mii_bus->phy_mask = ~(0x1f); smi->mii_bus->irq = smi->mii_irq; for (i = 0; i < PHY_MAX_ADDR; i++) smi->mii_irq[i] = PHY_POLL; ret = mdiobus_register(smi->mii_bus); if (ret) goto err_free; return 0; err_free: mdiobus_free(smi->mii_bus); err: return ret; } static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) { mdiobus_unregister(smi->mii_bus); mdiobus_free(smi->mii_bus); } int rtl8366_smi_init(struct rtl8366_smi *smi) { int err; if (!smi->parent) return -EINVAL; if (!smi->ops) return -EINVAL; err = gpio_request(smi->gpio_sda, dev_name(smi->parent)); if (err) { dev_err(smi->parent, "gpio_request failed for %u, err=%d\n", smi->gpio_sda, err); goto err_out; } err = gpio_request(smi->gpio_sck, dev_name(smi->parent)); if (err) { dev_err(smi->parent, "gpio_request failed for %u, err=%d\n", smi->gpio_sck, err); goto err_free_sda; } spin_lock_init(&smi->lock); dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", smi->gpio_sda, smi->gpio_sck); err = smi->ops->detect(smi); if (err) { dev_err(smi->parent, "chip detection failed, err=%d\n", err); goto err_free_sck; } err = rtl8366_smi_mii_init(smi); if (err) goto err_free_sck; return 0; err_free_sck: gpio_free(smi->gpio_sck); err_free_sda: gpio_free(smi->gpio_sda); err_out: return err; } EXPORT_SYMBOL_GPL(rtl8366_smi_init); void rtl8366_smi_cleanup(struct rtl8366_smi *smi) { rtl8366_smi_mii_cleanup(smi); gpio_free(smi->gpio_sck); gpio_free(smi->gpio_sda); } EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); MODULE_AUTHOR("Gabor Juhos "); MODULE_LICENSE("GPL v2");