aboutsummaryrefslogtreecommitdiff
path: root/drivers/ata/sata_phy.c
blob: 53d441775d74e17dfda5b27210c9112fbebab4bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
 * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd.
 *              http://www.samsung.com
 *
 * SATA PHY framework.
 *
 * This file provides a set of functions/interfaces for establishing
 * communication between SATA controller and the PHY controller. A
 * PHY controller driver registers call backs for its initialization and
 * shutdown. The SATA controller driver finds the appropriate PHYs for
 * its implemented ports and initialize/shutdown PHYs through the
 * call backs provided.
 *
 * 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 <linux/kernel.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
#include "sata_phy.h"

static LIST_HEAD(phy_list);
static DEFINE_SPINLOCK(phy_lock);

struct sata_phy *sata_get_phy(struct device_node *phy_np)
{
	struct sata_phy *phy;
	unsigned long flag;

	spin_lock_irqsave(&phy_lock, flag);

	if (list_empty(&phy_list)) {
		spin_unlock_irqrestore(&phy_lock, flag);
		return ERR_PTR(-ENODEV);
	}

	list_for_each_entry(phy, &phy_list, head) {
		if (phy->dev->of_node == phy_np) {
			if (phy->status == IN_USE) {
				pr_info(KERN_INFO
					"PHY already in use\n");
				spin_unlock_irqrestore(&phy_lock, flag);
				return ERR_PTR(-EBUSY);
			}

			get_device(phy->dev);
			phy->status = IN_USE;
			spin_unlock_irqrestore(&phy_lock, flag);
			return phy;
		}
	}

	spin_unlock_irqrestore(&phy_lock, flag);
	return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL(sata_get_phy);

int sata_add_phy(struct sata_phy *sataphy)
{
	unsigned long flag;
	unsigned int ret = -EINVAL;
	struct sata_phy *phy;

	if (!sataphy)
		return ret;

	spin_lock_irqsave(&phy_lock, flag);

	list_for_each_entry(phy, &phy_list, head) {
		if (phy->dev->of_node == sataphy->dev->of_node) {
			dev_err(sataphy->dev, "PHY already exists in the list\n");
			goto out;
		}
	}

	sataphy->status = NOT_IN_USE;
	list_add_tail(&sataphy->head, &phy_list);
	ret = 0;

 out:
	spin_unlock_irqrestore(&phy_lock, flag);
	return ret;
}
EXPORT_SYMBOL(sata_add_phy);

void sata_remove_phy(struct sata_phy *sataphy)
{
	unsigned long flag;
	struct sata_phy *phy;

	if (!sataphy)
		return;

	if (sataphy->status == IN_USE) {
		pr_info(KERN_INFO
			"PHY in use, cannot be removed\n");
		return;
	}

	spin_lock_irqsave(&phy_lock, flag);

	list_for_each_entry(phy, &phy_list, head) {
		if (phy->dev->of_node == sataphy->dev->of_node)
			list_del(&phy->head);
	}

	spin_unlock_irqrestore(&phy_lock, flag);
}
EXPORT_SYMBOL(sata_remove_phy);

void sata_put_phy(struct sata_phy *sataphy)
{
	unsigned long flag;

	if (!sataphy)
		return;

	spin_lock_irqsave(&phy_lock, flag);

	put_device(sataphy->dev);
	sataphy->status = NOT_IN_USE;

	spin_unlock_irqrestore(&phy_lock, flag);
}
EXPORT_SYMBOL(sata_put_phy);

int sata_init_phy(struct sata_phy *sataphy)
{
	if (sataphy && sataphy->init)
		return sataphy->init(sataphy);

	return -EINVAL;
}
EXPORT_SYMBOL(sata_init_phy);

void sata_shutdown_phy(struct sata_phy *sataphy)
{
	if (sataphy && sataphy->shutdown)
		sataphy->shutdown(sataphy);
}
EXPORT_SYMBOL(sata_shutdown_phy);