summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-u300/mmc.c
blob: 3138d3955c9eaabbaa24c62ec6fce9e90500ce31 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/*
 *
 * arch/arm/mach-u300/mmc.c
 *
 *
 * Copyright (C) 2009 ST-Ericsson AB
 * License terms: GNU General Public License (GPL) version 2
 *
 * Author: Linus Walleij <linus.walleij@stericsson.com>
 * Author: Johan Lundin <johan.lundin@stericsson.com>
 * Author: Jonas Aaberg <jonas.aberg@stericsson.com>
 */
#include <linux/device.h>
#include <linux/amba/bus.h>
#include <linux/mmc/host.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/machine.h>
#include <linux/gpio.h>

#include <asm/mach/mmc.h>
#include "mmc.h"

struct mmci_card_event {
	struct input_dev *mmc_input;
	int mmc_inserted;
	struct work_struct workq;
	struct mmc_platform_data mmc0_plat_data;
};

static unsigned int mmc_status(struct device *dev)
{
	struct mmci_card_event *mmci_card = container_of(
		dev->platform_data,
		struct mmci_card_event, mmc0_plat_data);

	return mmci_card->mmc_inserted;
}

/*
 * Here follows a large chunk of code which will only be enabled if you
 * have both the AB3100 chip mounted and the MMC subsystem activated.
 */

static u32 mmc_translate_vdd(struct device *dev, unsigned int voltage)
{
	int v;

	/*
	 * MMC Spec:
	 * bit 7:	1.70 - 1.95V
	 * bit 8 - 14:	2.0 - 2.6V
	 * bit 15 - 23:	2.7 - 3.6V
	 *
	 * ab3100 voltages:
	 * 000 - 2.85V
	 * 001 - 2.75V
	 * 010 - 1.8V
	 * 011 - 1.5V
	 */
	switch (voltage) {
	case 8:
		v = 3;
		break;
	case 9:
	case 10:
	case 11:
	case 12:
	case 13:
	case 14:
	case 15:
		v = 1;
		break;
	case 16:
		v = 1;
		break;
	case 17:
	case 18:
	case 19:
	case 20:
	case 21:
	case 22:
	case 23:
	case 24:
		v = 0;
		break;
	default:
		v = 0;
		break;
	}

	/* PL180 voltage register bits */
	return v << 2;
}



static int mmci_callback(void *data)
{
	struct mmci_card_event *mmci_card = data;

	disable_irq_on_gpio_pin(U300_GPIO_PIN_MMC_CD);
	schedule_work(&mmci_card->workq);

	return 0;
}


static ssize_t gpio_show(struct device *dev, struct device_attribute *attr,
		  char *buf)
{
	struct mmci_card_event *mmci_card = container_of(
		dev->platform_data,
		struct mmci_card_event, mmc0_plat_data);


	return sprintf(buf, "%d\n", !mmci_card->mmc_inserted);
}

static DEVICE_ATTR(mmc_inserted, S_IRUGO, gpio_show, NULL);

static void _mmci_callback(struct work_struct *ws)
{

	struct mmci_card_event *mmci_card = container_of(
		ws,
		struct mmci_card_event, workq);

	mdelay(20);

	mmci_card->mmc_inserted = !!gpio_get_value(U300_GPIO_PIN_MMC_CD);

	input_report_switch(mmci_card->mmc_input, KEY_INSERT,
			    !mmci_card->mmc_inserted);
	input_sync(mmci_card->mmc_input);

	pr_debug("MMC/SD card was %s\n",
		 mmci_card->mmc_inserted ? "removed" : "inserted");

	enable_irq_on_gpio_pin(U300_GPIO_PIN_MMC_CD, !mmci_card->mmc_inserted);
}

int __devinit mmc_init(struct amba_device *adev)
{
	struct mmci_card_event *mmci_card;
	struct device *mmcsd_device = &adev->dev;
	int ret = 0;

	mmci_card = kzalloc(sizeof(struct mmci_card_event), GFP_KERNEL);
	if (!mmci_card)
		return -ENOMEM;

	/* Nominally 2.85V on our platform */
	mmci_card->mmc0_plat_data.ocr_mask = MMC_VDD_28_29;
	mmci_card->mmc0_plat_data.translate_vdd = mmc_translate_vdd;
	mmci_card->mmc0_plat_data.status = mmc_status;

	mmcsd_device->platform_data = (void *) &mmci_card->mmc0_plat_data;

	INIT_WORK(&mmci_card->workq, _mmci_callback);

	ret = gpio_request(U300_GPIO_PIN_MMC_CD, "MMC card detection");
	if (ret) {
		printk(KERN_CRIT "Could not allocate MMC card detection " \
		       "GPIO pin\n");
		goto out;
	}

	ret = gpio_direction_input(U300_GPIO_PIN_MMC_CD);
	if (ret) {
		printk(KERN_CRIT "Invalid GPIO pin requested\n");
		goto out;
	}

	ret = sysfs_create_file(&mmcsd_device->kobj,
			       &dev_attr_mmc_inserted.attr);
	if (ret)
		goto out;

	mmci_card->mmc_input = input_allocate_device();
	if (!mmci_card->mmc_input) {
		printk(KERN_CRIT "Could not allocate MMC input device\n");
		return -ENOMEM;
	}

	mmci_card->mmc_input->name = "MMC insert notification";
	mmci_card->mmc_input->id.bustype = BUS_HOST;
	mmci_card->mmc_input->id.vendor = 0;
	mmci_card->mmc_input->id.product = 0;
	mmci_card->mmc_input->id.version = 0x0100;
	mmci_card->mmc_input->dev.parent = mmcsd_device;
	input_set_capability(mmci_card->mmc_input, EV_SW, KEY_INSERT);

	/*
	 * Since this must always be compiled into the kernel, this input
	 * is never unregistered or free:ed.
	 */
	ret = input_register_device(mmci_card->mmc_input);
	if (ret) {
		input_free_device(mmci_card->mmc_input);
		goto out;
	}

	input_set_drvdata(mmci_card->mmc_input, mmci_card);

	ret = gpio_register_callback(U300_GPIO_PIN_MMC_CD, mmci_callback,
				     mmci_card);

	schedule_work(&mmci_card->workq);

	printk(KERN_INFO "Registered MMC insert/remove notification\n");
out:
	return ret;
}
OpenPOWER on IntegriCloud