#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/if_bridge.h>
#include "realtek.h"
#include "realtek-smi.h"
#include "rtl83xx.h"
#define REALTEK_SMI_ACK_RETRY_COUNT 5
static inline void realtek_smi_clk_delay(struct realtek_priv *priv)
{
ndelay(priv->variant->clk_delay);
}
static void realtek_smi_start(struct realtek_priv *priv)
{
gpiod_direction_output(priv->mdc, 0);
gpiod_direction_output(priv->mdio, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
}
static void realtek_smi_stop(struct realtek_priv *priv)
{
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
gpiod_direction_input(priv->mdio);
gpiod_direction_input(priv->mdc);
}
static void realtek_smi_write_bits(struct realtek_priv *priv, u32 data, u32 len)
{
for (; len > 0; len--) {
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, !!(data & (1 << (len - 1))));
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
}
}
static void realtek_smi_read_bits(struct realtek_priv *priv, u32 len, u32 *data)
{
gpiod_direction_input(priv->mdio);
for (*data = 0; len > 0; len--) {
u32 u;
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
u = !!gpiod_get_value(priv->mdio);
gpiod_set_value(priv->mdc, 0);
*data |= (u << (len - 1));
}
gpiod_direction_output(priv->mdio, 0);
}
static int realtek_smi_wait_for_ack(struct realtek_priv *priv)
{
int retry_cnt;
retry_cnt = 0;
do {
u32 ack;
realtek_smi_read_bits(priv, 1, &ack);
if (ack == 0)
break;
if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) {
dev_err(priv->dev, "ACK timeout\n");
return -ETIMEDOUT;
}
} while (1);
return 0;
}
static int realtek_smi_write_byte(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return realtek_smi_wait_for_ack(priv);
}
static int realtek_smi_write_byte_noack(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return 0;
}
static int realtek_smi_read_byte0(struct realtek_priv *priv, u8 *data)
{
u32 t;
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
realtek_smi_write_bits(priv, 0x00, 1);
return 0;
}
static int realtek_smi_read_byte1(struct realtek_priv *priv, u8 *data)
{
u32 t;
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
realtek_smi_write_bits(priv, 0x01, 1);
return 0;
}
static int realtek_smi_read_reg(struct realtek_priv *priv, u32 addr, u32 *data)
{
unsigned long flags;
u8 lo = 0;
u8 hi = 0;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
ret = realtek_smi_write_byte(priv, priv->variant->cmd_read);
if (ret)
goto out;
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
realtek_smi_read_byte0(priv, &lo);
realtek_smi_read_byte1(priv, &hi);
*data = ((u32)lo) | (((u32)hi) << 8);
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
static int realtek_smi_write_reg(struct realtek_priv *priv,
u32 addr, u32 data, bool ack)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
ret = realtek_smi_write_byte(priv, priv->variant->cmd_write);
if (ret)
goto out;
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
ret = realtek_smi_write_byte(priv, data & 0xff);
if (ret)
goto out;
if (ack)
ret = realtek_smi_write_byte(priv, data >> 8);
else
ret = realtek_smi_write_byte_noack(priv, data >> 8);
if (ret)
goto out;
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
static int realtek_smi_write_reg_noack(void *ctx, u32 reg, u32 val)
{
return realtek_smi_write_reg(ctx, reg, val, false);
}
static int realtek_smi_write(void *ctx, u32 reg, u32 val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_write_reg(priv, reg, val, true);
}
static int realtek_smi_read(void *ctx, u32 reg, u32 *val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_read_reg(priv, reg, val);
}
static const struct realtek_interface_info realtek_smi_info = {
.reg_read = realtek_smi_read,
.reg_write = realtek_smi_write,
};
int realtek_smi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct realtek_priv *priv;
int ret;
priv = rtl83xx_probe(dev, &realtek_smi_info);
if (IS_ERR(priv))
return PTR_ERR(priv);
priv->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdc)) {
rtl83xx_remove(priv);
return PTR_ERR(priv->mdc);
}
priv->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdio)) {
rtl83xx_remove(priv);
return PTR_ERR(priv->mdio);
}
priv->write_reg_noack = realtek_smi_write_reg_noack;
ret = rtl83xx_register_switch(priv);
if (ret) {
rtl83xx_remove(priv);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, "REALTEK_DSA");
void realtek_smi_remove(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
rtl83xx_unregister_switch(priv);
rtl83xx_remove(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, "REALTEK_DSA");
void realtek_smi_shutdown(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
rtl83xx_shutdown(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, "REALTEK_DSA");