1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IIO DAC driver for NXP LPC18xx DAC |
4 | * |
5 | * Copyright (C) 2016 Joachim Eastwood <manabian@gmail.com> |
6 | * |
7 | * UNSUPPORTED hardware features: |
8 | * - Interrupts |
9 | * - DMA |
10 | */ |
11 | |
12 | #include <linux/clk.h> |
13 | #include <linux/err.h> |
14 | #include <linux/iio/iio.h> |
15 | #include <linux/iio/driver.h> |
16 | #include <linux/io.h> |
17 | #include <linux/iopoll.h> |
18 | #include <linux/module.h> |
19 | #include <linux/mod_devicetable.h> |
20 | #include <linux/mutex.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/regulator/consumer.h> |
23 | |
24 | /* LPC18XX DAC registers and bits */ |
25 | #define LPC18XX_DAC_CR 0x000 |
26 | #define LPC18XX_DAC_CR_VALUE_SHIFT 6 |
27 | #define LPC18XX_DAC_CR_VALUE_MASK 0x3ff |
28 | #define LPC18XX_DAC_CR_BIAS BIT(16) |
29 | #define LPC18XX_DAC_CTRL 0x004 |
30 | #define LPC18XX_DAC_CTRL_DMA_ENA BIT(3) |
31 | |
32 | struct lpc18xx_dac { |
33 | struct regulator *vref; |
34 | void __iomem *base; |
35 | struct mutex lock; |
36 | struct clk *clk; |
37 | }; |
38 | |
39 | static const struct iio_chan_spec lpc18xx_dac_iio_channels[] = { |
40 | { |
41 | .type = IIO_VOLTAGE, |
42 | .output = 1, |
43 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
44 | BIT(IIO_CHAN_INFO_SCALE), |
45 | }, |
46 | }; |
47 | |
48 | static int lpc18xx_dac_read_raw(struct iio_dev *indio_dev, |
49 | struct iio_chan_spec const *chan, |
50 | int *val, int *val2, long mask) |
51 | { |
52 | struct lpc18xx_dac *dac = iio_priv(indio_dev); |
53 | u32 reg; |
54 | |
55 | switch (mask) { |
56 | case IIO_CHAN_INFO_RAW: |
57 | reg = readl(addr: dac->base + LPC18XX_DAC_CR); |
58 | *val = reg >> LPC18XX_DAC_CR_VALUE_SHIFT; |
59 | *val &= LPC18XX_DAC_CR_VALUE_MASK; |
60 | |
61 | return IIO_VAL_INT; |
62 | |
63 | case IIO_CHAN_INFO_SCALE: |
64 | *val = regulator_get_voltage(regulator: dac->vref) / 1000; |
65 | *val2 = 10; |
66 | |
67 | return IIO_VAL_FRACTIONAL_LOG2; |
68 | } |
69 | |
70 | return -EINVAL; |
71 | } |
72 | |
73 | static int lpc18xx_dac_write_raw(struct iio_dev *indio_dev, |
74 | struct iio_chan_spec const *chan, |
75 | int val, int val2, long mask) |
76 | { |
77 | struct lpc18xx_dac *dac = iio_priv(indio_dev); |
78 | u32 reg; |
79 | |
80 | switch (mask) { |
81 | case IIO_CHAN_INFO_RAW: |
82 | if (val < 0 || val > LPC18XX_DAC_CR_VALUE_MASK) |
83 | return -EINVAL; |
84 | |
85 | reg = LPC18XX_DAC_CR_BIAS; |
86 | reg |= val << LPC18XX_DAC_CR_VALUE_SHIFT; |
87 | |
88 | mutex_lock(&dac->lock); |
89 | writel(val: reg, addr: dac->base + LPC18XX_DAC_CR); |
90 | writel(LPC18XX_DAC_CTRL_DMA_ENA, addr: dac->base + LPC18XX_DAC_CTRL); |
91 | mutex_unlock(lock: &dac->lock); |
92 | |
93 | return 0; |
94 | } |
95 | |
96 | return -EINVAL; |
97 | } |
98 | |
99 | static const struct iio_info lpc18xx_dac_info = { |
100 | .read_raw = lpc18xx_dac_read_raw, |
101 | .write_raw = lpc18xx_dac_write_raw, |
102 | }; |
103 | |
104 | static int lpc18xx_dac_probe(struct platform_device *pdev) |
105 | { |
106 | struct iio_dev *indio_dev; |
107 | struct lpc18xx_dac *dac; |
108 | int ret; |
109 | |
110 | indio_dev = devm_iio_device_alloc(parent: &pdev->dev, sizeof_priv: sizeof(*dac)); |
111 | if (!indio_dev) |
112 | return -ENOMEM; |
113 | |
114 | platform_set_drvdata(pdev, data: indio_dev); |
115 | dac = iio_priv(indio_dev); |
116 | mutex_init(&dac->lock); |
117 | |
118 | dac->base = devm_platform_ioremap_resource(pdev, index: 0); |
119 | if (IS_ERR(ptr: dac->base)) |
120 | return PTR_ERR(ptr: dac->base); |
121 | |
122 | dac->clk = devm_clk_get(dev: &pdev->dev, NULL); |
123 | if (IS_ERR(ptr: dac->clk)) |
124 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: dac->clk), |
125 | fmt: "error getting clock\n" ); |
126 | |
127 | dac->vref = devm_regulator_get(dev: &pdev->dev, id: "vref" ); |
128 | if (IS_ERR(ptr: dac->vref)) |
129 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: dac->vref), |
130 | fmt: "error getting regulator\n" ); |
131 | |
132 | indio_dev->name = dev_name(dev: &pdev->dev); |
133 | indio_dev->info = &lpc18xx_dac_info; |
134 | indio_dev->modes = INDIO_DIRECT_MODE; |
135 | indio_dev->channels = lpc18xx_dac_iio_channels; |
136 | indio_dev->num_channels = ARRAY_SIZE(lpc18xx_dac_iio_channels); |
137 | |
138 | ret = regulator_enable(regulator: dac->vref); |
139 | if (ret) { |
140 | dev_err(&pdev->dev, "unable to enable regulator\n" ); |
141 | return ret; |
142 | } |
143 | |
144 | ret = clk_prepare_enable(clk: dac->clk); |
145 | if (ret) { |
146 | dev_err(&pdev->dev, "unable to enable clock\n" ); |
147 | goto dis_reg; |
148 | } |
149 | |
150 | writel(val: 0, addr: dac->base + LPC18XX_DAC_CTRL); |
151 | writel(val: 0, addr: dac->base + LPC18XX_DAC_CR); |
152 | |
153 | ret = iio_device_register(indio_dev); |
154 | if (ret) { |
155 | dev_err(&pdev->dev, "unable to register device\n" ); |
156 | goto dis_clk; |
157 | } |
158 | |
159 | return 0; |
160 | |
161 | dis_clk: |
162 | clk_disable_unprepare(clk: dac->clk); |
163 | dis_reg: |
164 | regulator_disable(regulator: dac->vref); |
165 | return ret; |
166 | } |
167 | |
168 | static void lpc18xx_dac_remove(struct platform_device *pdev) |
169 | { |
170 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); |
171 | struct lpc18xx_dac *dac = iio_priv(indio_dev); |
172 | |
173 | iio_device_unregister(indio_dev); |
174 | |
175 | writel(val: 0, addr: dac->base + LPC18XX_DAC_CTRL); |
176 | clk_disable_unprepare(clk: dac->clk); |
177 | regulator_disable(regulator: dac->vref); |
178 | } |
179 | |
180 | static const struct of_device_id lpc18xx_dac_match[] = { |
181 | { .compatible = "nxp,lpc1850-dac" }, |
182 | { /* sentinel */ } |
183 | }; |
184 | MODULE_DEVICE_TABLE(of, lpc18xx_dac_match); |
185 | |
186 | static struct platform_driver lpc18xx_dac_driver = { |
187 | .probe = lpc18xx_dac_probe, |
188 | .remove_new = lpc18xx_dac_remove, |
189 | .driver = { |
190 | .name = "lpc18xx-dac" , |
191 | .of_match_table = lpc18xx_dac_match, |
192 | }, |
193 | }; |
194 | module_platform_driver(lpc18xx_dac_driver); |
195 | |
196 | MODULE_DESCRIPTION("LPC18xx DAC driver" ); |
197 | MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>" ); |
198 | MODULE_LICENSE("GPL v2" ); |
199 | |