summaryrefslogtreecommitdiff
path: root/io-module/adc.c
blob: 3d5f3d07deb0be03848d864603bd142d3ffd42c3 (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
#define AT91SAM9X5_BASE_ADC     0xf804c000
#define AT91SAM9260_BASE_ADC     0xfffe0000

#define ADC_SHTIME_DEFAULT			0x05
#define ADC_STARTUP_DEFAULT			0x04
#define ADC_PRESCALE_DEFAULT			0x3F
#define ADC_MODE_DEFAULT \
	((ADC_SHTIME_DEFAULT & 0x0F) << 24) | \
	((ADC_STARTUP_DEFAULT & 0x1F) << 16) | \
	((ADC_PRESCALE_DEFAULT & 0x3F) << 8)

#define ADC_CR_OFFSET			0x00
#define ADC_MR_OFFSET			0x04
#define ADC_CHER_OFFSET			0x10
#define ADC_CHDR_OFFSET			0x14
#define ADC_CHSR_OFFSET			0x18
#define ADC_SR_OFFSET			0x1C
#define ADC_LDCR_OFFSET			0x20
#define ADC_IER_OFFSET			0x14
#define ADC_IDR_OFFSET			0x28
#define ADC_IMR_OFFSET			0x2C
#define ADC_CDR0_OFFSET			0x30
#define ADC_CDR1_OFFSET			0x34
#define ADC_CDR2_OFFSET			0x38
#define ADC_CDR3_OFFSET			0x3C

void __iomem *adc_base;
struct clk *adc_clk;

#define ADC_CONVERT_RESET(base)			writel(0x01, (base) + ADC_CR_OFFSET)
#define ADC_CONVERT_START(base)			writel(0x02, (base) + ADC_CR_OFFSET)

static ssize_t mts_attr_show_adc(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int offset;
	u32 value;
	u32 chan_mask;

	if (!DEVICE_CAPA(id_eeprom.capa, CAPA_ADC)) {
		log_debug("ADC not available");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "adc0")) {
		offset = ADC_CDR0_OFFSET;
		chan_mask = 0x01;
	} else if (!strcmp(attr->attr.name, "adc1")) {
		offset = ADC_CDR1_OFFSET;
		chan_mask = 0x02;
	} else if (!strcmp(attr->attr.name, "adc2")) {
		offset = ADC_CDR2_OFFSET;
		chan_mask = 0x04;
	} else if (!strcmp(attr->attr.name, "adc3")) {
		offset = ADC_CDR3_OFFSET;
		chan_mask = 0x08;
	} else {
		log_notice("adc attr does not exist");
		return -ENOENT;
	}

	mutex_lock(&mts_io_mutex);

	// disable all channels and enable the one we want
	writel(0x0F, adc_base + ADC_CHDR_OFFSET);
	writel(chan_mask, adc_base + ADC_CHER_OFFSET);

	ADC_CONVERT_START(adc_base);

	// wait for conversion to complete (EOC bit set)
	value = 0;
	while (value != chan_mask) {
		value = readl(adc_base + ADC_SR_OFFSET) & chan_mask;
		log_debug("ADC_SR EOC [%X]", value);
	}

	// read result
	value = readl(adc_base + offset);

	mutex_unlock(&mts_io_mutex);

	return sprintf(buf, "%lu\n", (unsigned long) value);
}

static DEVICE_ATTR_RO_MTS(dev_attr_adc0, "adc0", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc1, "adc1", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc2, "adc2", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc3, "adc3", mts_attr_show_adc);

static int mts_io_board_adc_probe(struct platform_device *pdev)
{
	int err;
	adc_clk = clk_get(&pdev->dev, "adc_clk");
	if (adc_clk) {						
		err = clk_prepare_enable(adc_clk);
		if (err) {
			log_error("clock failed to prepare+enable: %d\n", err);
			return err;
		}
		#ifdef CONFIG_SOC_AT91SAM9X5
		adc_base = ioremap(AT91SAM9X5_BASE_ADC, SZ_16K);
		#else
		adc_base = ioremap(AT91SAM9260_BASE_ADC, SZ_16K);
		#endif
		if (!adc_base) {
			log_error("ioremap failed.\n");
			return -ENOMEM;
		}
		ADC_CONVERT_RESET(adc_base);
		writel(ADC_MODE_DEFAULT, adc_base + ADC_MR_OFFSET);
		writel(0x000F0F0F, adc_base + ADC_IDR_OFFSET);
		writel(0x0F, adc_base + ADC_CHDR_OFFSET);				
	}
	return 0;
}

static int mts_io_board_adc_remove(struct platform_device *pdev)
{
	if (adc_clk) {
		int ret;
		clk_disable_unprepare(adc_clk);									
	}
	return 0;
}