fixed.c 6.8 KB
Newer Older
1
/*
2
 * Fixed MDIO bus (MDIO bus emulation with fixed PHYs)
3
 *
4 5
 * Author: Vitaly Bordug <vbordug@ru.mvista.com>
 *         Anton Vorontsov <avorontsov@ru.mvista.com>
6
 *
7
 * Copyright (c) 2006-2007 MontaVista Software, Inc.
8 9 10 11 12 13
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */
14

15 16
#include <linux/kernel.h>
#include <linux/module.h>
17 18
#include <linux/platform_device.h>
#include <linux/list.h>
19 20
#include <linux/mii.h>
#include <linux/phy.h>
21
#include <linux/phy_fixed.h>
22
#include <linux/err.h>
23
#include <linux/slab.h>
24
#include <linux/of.h>
25

26
#define MII_REGS_NUM 29
27

28 29
struct fixed_mdio_bus {
	int irqs[PHY_MAX_ADDR];
30
	struct mii_bus *mii_bus;
31 32
	struct list_head phys;
};
33

34
struct fixed_phy {
35
	int addr;
36 37 38 39 40 41
	u16 regs[MII_REGS_NUM];
	struct phy_device *phydev;
	struct fixed_phy_status status;
	int (*link_update)(struct net_device *, struct fixed_phy_status *);
	struct list_head node;
};
42

43 44 45 46
static struct platform_device *pdev;
static struct fixed_mdio_bus platform_fmb = {
	.phys = LIST_HEAD_INIT(platform_fmb.phys),
};
47

48
static int fixed_phy_update_regs(struct fixed_phy *fp)
49
{
50
	u16 bmsr = BMSR_ANEGCAPABLE;
51
	u16 bmcr = 0;
52 53
	u16 lpagb = 0;
	u16 lpa = 0;
54

55
	if (fp->status.duplex) {
56 57
		bmcr |= BMCR_FULLDPLX;

58 59 60 61 62 63
		switch (fp->status.speed) {
		case 1000:
			bmsr |= BMSR_ESTATEN;
			bmcr |= BMCR_SPEED1000;
			lpagb |= LPA_1000FULL;
			break;
64 65 66
		case 100:
			bmsr |= BMSR_100FULL;
			bmcr |= BMCR_SPEED100;
67
			lpa |= LPA_100FULL;
68
			break;
69 70
		case 10:
			bmsr |= BMSR_10FULL;
71
			lpa |= LPA_10FULL;
72
			break;
73
		default:
J
Joe Perches 已提交
74
			pr_warn("fixed phy: unknown speed\n");
75
			return -EINVAL;
76 77
		}
	} else {
78 79 80 81 82 83
		switch (fp->status.speed) {
		case 1000:
			bmsr |= BMSR_ESTATEN;
			bmcr |= BMCR_SPEED1000;
			lpagb |= LPA_1000HALF;
			break;
84 85 86
		case 100:
			bmsr |= BMSR_100HALF;
			bmcr |= BMCR_SPEED100;
87
			lpa |= LPA_100HALF;
88
			break;
89
		case 10:
90 91
			bmsr |= BMSR_10HALF;
			lpa |= LPA_10HALF;
92
			break;
93
		default:
J
Joe Perches 已提交
94
			pr_warn("fixed phy: unknown speed\n");
95
			return -EINVAL;
96 97 98
		}
	}

99 100 101 102 103 104 105 106 107
	if (fp->status.link)
		bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;

	if (fp->status.pause)
		lpa |= LPA_PAUSE_CAP;

	if (fp->status.asym_pause)
		lpa |= LPA_PAUSE_ASYM;

108 109
	fp->regs[MII_PHYSID1] = 0;
	fp->regs[MII_PHYSID2] = 0;
110 111 112 113 114

	fp->regs[MII_BMSR] = bmsr;
	fp->regs[MII_BMCR] = bmcr;
	fp->regs[MII_LPA] = lpa;
	fp->regs[MII_STAT1000] = lpagb;
115 116 117 118

	return 0;
}

119
static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
120
{
121
	struct fixed_mdio_bus *fmb = bus->priv;
122 123 124 125 126 127
	struct fixed_phy *fp;

	if (reg_num >= MII_REGS_NUM)
		return -1;

	list_for_each_entry(fp, &fmb->phys, node) {
128
		if (fp->addr == phy_addr) {
129 130 131 132 133
			/* Issue callback if user registered it. */
			if (fp->link_update) {
				fp->link_update(fp->phydev->attached_dev,
						&fp->status);
				fixed_phy_update_regs(fp);
134
			}
135
			return fp->regs[reg_num];
136
		}
137
	}
138

139
	return 0xFFFF;
140 141
}

142
static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num,
143
			    u16 val)
144 145 146 147
{
	return 0;
}

148 149 150 151 152 153 154 155
/*
 * If something weird is required to be done with link/speed,
 * network driver is able to assign a function to implement this.
 * May be useful for PHY's that need to be software-driven.
 */
int fixed_phy_set_link_update(struct phy_device *phydev,
			      int (*link_update)(struct net_device *,
						 struct fixed_phy_status *))
156
{
157 158
	struct fixed_mdio_bus *fmb = &platform_fmb;
	struct fixed_phy *fp;
159

160 161
	if (!link_update || !phydev || !phydev->bus)
		return -EINVAL;
162

163
	list_for_each_entry(fp, &fmb->phys, node) {
164
		if (fp->addr == phydev->addr) {
165 166 167 168 169
			fp->link_update = link_update;
			fp->phydev = phydev;
			return 0;
		}
	}
170

171
	return -ENOENT;
172
}
173
EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
174

175
int fixed_phy_add(unsigned int irq, int phy_addr,
176
		  struct fixed_phy_status *status)
177
{
178 179 180
	int ret;
	struct fixed_mdio_bus *fmb = &platform_fmb;
	struct fixed_phy *fp;
181

182 183 184
	fp = kzalloc(sizeof(*fp), GFP_KERNEL);
	if (!fp)
		return -ENOMEM;
185

186
	memset(fp->regs, 0xFF,  sizeof(fp->regs[0]) * MII_REGS_NUM);
187

188
	fmb->irqs[phy_addr] = irq;
189

190
	fp->addr = phy_addr;
191
	fp->status = *status;
192

193 194 195
	ret = fixed_phy_update_regs(fp);
	if (ret)
		goto err_regs;
196

197
	list_add_tail(&fp->node, &fmb->phys);
198

199
	return 0;
200

201 202 203 204 205
err_regs:
	kfree(fp);
	return ret;
}
EXPORT_SYMBOL_GPL(fixed_phy_add);
206

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
void fixed_phy_del(int phy_addr)
{
	struct fixed_mdio_bus *fmb = &platform_fmb;
	struct fixed_phy *fp, *tmp;

	list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
		if (fp->addr == phy_addr) {
			list_del(&fp->node);
			kfree(fp);
			return;
		}
	}
}
EXPORT_SYMBOL_GPL(fixed_phy_del);

static int phy_fixed_addr;
static DEFINE_SPINLOCK(phy_fixed_addr_lock);

int fixed_phy_register(unsigned int irq,
		       struct fixed_phy_status *status,
		       struct device_node *np)
{
	struct fixed_mdio_bus *fmb = &platform_fmb;
	struct phy_device *phy;
	int phy_addr;
	int ret;

	/* Get the next available PHY address, up to PHY_MAX_ADDR */
	spin_lock(&phy_fixed_addr_lock);
	if (phy_fixed_addr == PHY_MAX_ADDR) {
		spin_unlock(&phy_fixed_addr_lock);
		return -ENOSPC;
	}
	phy_addr = phy_fixed_addr++;
	spin_unlock(&phy_fixed_addr_lock);

	ret = fixed_phy_add(PHY_POLL, phy_addr, status);
	if (ret < 0)
		return ret;

	phy = get_phy_device(fmb->mii_bus, phy_addr, false);
	if (!phy || IS_ERR(phy)) {
		fixed_phy_del(phy_addr);
		return -EINVAL;
	}

	of_node_get(np);
	phy->dev.of_node = np;

	ret = phy_device_register(phy);
	if (ret) {
		phy_device_free(phy);
		of_node_put(np);
		fixed_phy_del(phy_addr);
		return ret;
	}

	return 0;
}

267 268 269 270
static int __init fixed_mdio_bus_init(void)
{
	struct fixed_mdio_bus *fmb = &platform_fmb;
	int ret;
271

272
	pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0);
273 274
	if (IS_ERR(pdev)) {
		ret = PTR_ERR(pdev);
275 276
		goto err_pdev;
	}
277

278 279 280 281 282
	fmb->mii_bus = mdiobus_alloc();
	if (fmb->mii_bus == NULL) {
		ret = -ENOMEM;
		goto err_mdiobus_reg;
	}
283

284
	snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0");
285
	fmb->mii_bus->name = "Fixed MDIO Bus";
286
	fmb->mii_bus->priv = fmb;
287 288 289 290 291 292
	fmb->mii_bus->parent = &pdev->dev;
	fmb->mii_bus->read = &fixed_mdio_read;
	fmb->mii_bus->write = &fixed_mdio_write;
	fmb->mii_bus->irq = fmb->irqs;

	ret = mdiobus_register(fmb->mii_bus);
293
	if (ret)
294
		goto err_mdiobus_alloc;
295

296
	return 0;
297

298 299
err_mdiobus_alloc:
	mdiobus_free(fmb->mii_bus);
300 301 302 303 304 305
err_mdiobus_reg:
	platform_device_unregister(pdev);
err_pdev:
	return ret;
}
module_init(fixed_mdio_bus_init);
306

307 308 309
static void __exit fixed_mdio_bus_exit(void)
{
	struct fixed_mdio_bus *fmb = &platform_fmb;
310
	struct fixed_phy *fp, *tmp;
311

312 313
	mdiobus_unregister(fmb->mii_bus);
	mdiobus_free(fmb->mii_bus);
314
	platform_device_unregister(pdev);
315

316
	list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
317 318
		list_del(&fp->node);
		kfree(fp);
319
	}
320
}
321
module_exit(fixed_mdio_bus_exit);
322

323
MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)");
324 325
MODULE_AUTHOR("Vitaly Bordug");
MODULE_LICENSE("GPL");