summaryrefslogtreecommitdiffstats
path: root/drivers/mfd/ac100.c
blob: 9bc69cd7807d04a67a614f05992155c07f04bf1b (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
/*
 * MFD core driver for X-Powers' AC100 Audio Codec IC
 *
 * The AC100 is a highly integrated audio codec and RTC subsystem designed
 * for mobile applications. It has 3 I2S/PCM interfaces, a 2 channel DAC,
 * a 2 channel ADC with 5 inputs and a builtin mixer. The RTC subsystem has
 * 3 clock outputs.
 *
 * The audio codec and RTC parts are completely separate, sharing only the
 * host interface for access to its registers.
 *
 * Copyright (2016) Chen-Yu Tsai
 *
 * Author: Chen-Yu Tsai <wens@csie.org>
 *
 * 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/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/core.h>
#include <linux/mfd/ac100.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/sunxi-rsb.h>

static const struct regmap_range ac100_writeable_ranges[] = {
	regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_I2S_SR_CTRL),
	regmap_reg_range(AC100_I2S1_CLK_CTRL, AC100_I2S1_MXR_GAIN),
	regmap_reg_range(AC100_I2S2_CLK_CTRL, AC100_I2S2_MXR_GAIN),
	regmap_reg_range(AC100_I2S3_CLK_CTRL, AC100_I2S3_SIG_PATH_CTRL),
	regmap_reg_range(AC100_ADC_DIG_CTRL, AC100_ADC_VOL_CTRL),
	regmap_reg_range(AC100_HMIC_CTRL1, AC100_HMIC_STATUS),
	regmap_reg_range(AC100_DAC_DIG_CTRL, AC100_DAC_MXR_GAIN),
	regmap_reg_range(AC100_ADC_APC_CTRL, AC100_LINEOUT_CTRL),
	regmap_reg_range(AC100_ADC_DAP_L_CTRL, AC100_ADC_DAP_OPT),
	regmap_reg_range(AC100_DAC_DAP_CTRL, AC100_DAC_DAP_OPT),
	regmap_reg_range(AC100_ADC_DAP_ENA, AC100_DAC_DAP_ENA),
	regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL2),
	regmap_reg_range(AC100_SRC2_CTRL1, AC100_SRC2_CTRL2),
	regmap_reg_range(AC100_CLK32K_ANALOG_CTRL, AC100_CLKOUT_CTRL3),
	regmap_reg_range(AC100_RTC_RST, AC100_RTC_UPD),
	regmap_reg_range(AC100_ALM_INT_ENA, AC100_ALM_INT_STA),
	regmap_reg_range(AC100_ALM_SEC, AC100_RTC_GP(15)),
};

static const struct regmap_range ac100_volatile_ranges[] = {
	regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_PLL_CTRL2),
	regmap_reg_range(AC100_HMIC_STATUS, AC100_HMIC_STATUS),
	regmap_reg_range(AC100_ADC_DAP_L_STA, AC100_ADC_DAP_L_STA),
	regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL1),
	regmap_reg_range(AC100_SRC1_CTRL3, AC100_SRC2_CTRL1),
	regmap_reg_range(AC100_SRC2_CTRL3, AC100_SRC2_CTRL4),
	regmap_reg_range(AC100_RTC_RST, AC100_RTC_RST),
	regmap_reg_range(AC100_RTC_SEC, AC100_ALM_INT_STA),
	regmap_reg_range(AC100_ALM_SEC, AC100_ALM_UPD),
};

static const struct regmap_access_table ac100_writeable_table = {
	.yes_ranges	= ac100_writeable_ranges,
	.n_yes_ranges	= ARRAY_SIZE(ac100_writeable_ranges),
};

static const struct regmap_access_table ac100_volatile_table = {
	.yes_ranges	= ac100_volatile_ranges,
	.n_yes_ranges	= ARRAY_SIZE(ac100_volatile_ranges),
};

static const struct regmap_config ac100_regmap_config = {
	.reg_bits	= 8,
	.val_bits	= 16,
	.wr_table	= &ac100_writeable_table,
	.volatile_table	= &ac100_volatile_table,
	.max_register	= AC100_RTC_GP(15),
	.cache_type	= REGCACHE_RBTREE,
};

static struct mfd_cell ac100_cells[] = {
	{
		.name		= "ac100-codec",
		.of_compatible	= "x-powers,ac100-codec",
	}, {
		.name		= "ac100-rtc",
		.of_compatible	= "x-powers,ac100-rtc",
	},
};

static int ac100_rsb_probe(struct sunxi_rsb_device *rdev)
{
	struct ac100_dev *ac100;
	int ret;

	ac100 = devm_kzalloc(&rdev->dev, sizeof(*ac100), GFP_KERNEL);
	if (!ac100)
		return -ENOMEM;

	ac100->dev = &rdev->dev;
	sunxi_rsb_device_set_drvdata(rdev, ac100);

	ac100->regmap = devm_regmap_init_sunxi_rsb(rdev, &ac100_regmap_config);
	if (IS_ERR(ac100->regmap)) {
		ret = PTR_ERR(ac100->regmap);
		dev_err(ac100->dev, "regmap init failed: %d\n", ret);
		return ret;
	}

	ret = devm_mfd_add_devices(ac100->dev, PLATFORM_DEVID_NONE, ac100_cells,
				   ARRAY_SIZE(ac100_cells), NULL, 0, NULL);
	if (ret) {
		dev_err(ac100->dev, "failed to add MFD devices: %d\n", ret);
		return ret;
	}

	return 0;
}

static const struct of_device_id ac100_of_match[] = {
	{ .compatible = "x-powers,ac100" },
	{ },
};
MODULE_DEVICE_TABLE(of, ac100_of_match);

static struct sunxi_rsb_driver ac100_rsb_driver = {
	.driver = {
		.name	= "ac100",
		.of_match_table	= of_match_ptr(ac100_of_match),
	},
	.probe	= ac100_rsb_probe,
};
module_sunxi_rsb_driver(ac100_rsb_driver);

MODULE_DESCRIPTION("Audio codec MFD core driver for AC100");
MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
MODULE_LICENSE("GPL v2");
OpenPOWER on IntegriCloud