1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // loongson_i2s_pci.c -- Loongson I2S controller driver |
4 | // |
5 | // Copyright (C) 2023 Loongson Technology Corporation Limited |
6 | // Author: Yingkun Meng <mengyingkun@loongson.cn> |
7 | // |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/dma-mapping.h> |
13 | #include <linux/acpi.h> |
14 | #include <linux/pci.h> |
15 | #include <sound/soc.h> |
16 | #include "loongson_i2s.h" |
17 | #include "loongson_dma.h" |
18 | |
19 | static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg) |
20 | { |
21 | switch (reg) { |
22 | case LS_I2S_CFG: |
23 | case LS_I2S_CTRL: |
24 | case LS_I2S_RX_DATA: |
25 | case LS_I2S_TX_DATA: |
26 | case LS_I2S_CFG1: |
27 | return true; |
28 | default: |
29 | return false; |
30 | }; |
31 | } |
32 | |
33 | static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg) |
34 | { |
35 | switch (reg) { |
36 | case LS_I2S_VER: |
37 | case LS_I2S_CFG: |
38 | case LS_I2S_CTRL: |
39 | case LS_I2S_RX_DATA: |
40 | case LS_I2S_TX_DATA: |
41 | case LS_I2S_CFG1: |
42 | return true; |
43 | default: |
44 | return false; |
45 | }; |
46 | } |
47 | |
48 | static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg) |
49 | { |
50 | switch (reg) { |
51 | case LS_I2S_CFG: |
52 | case LS_I2S_CTRL: |
53 | case LS_I2S_RX_DATA: |
54 | case LS_I2S_TX_DATA: |
55 | case LS_I2S_CFG1: |
56 | return true; |
57 | default: |
58 | return false; |
59 | }; |
60 | } |
61 | |
62 | static const struct regmap_config loongson_i2s_regmap_config = { |
63 | .reg_bits = 32, |
64 | .reg_stride = 4, |
65 | .val_bits = 32, |
66 | .max_register = LS_I2S_CFG1, |
67 | .writeable_reg = loongson_i2s_wr_reg, |
68 | .readable_reg = loongson_i2s_rd_reg, |
69 | .volatile_reg = loongson_i2s_volatile_reg, |
70 | .cache_type = REGCACHE_FLAT, |
71 | }; |
72 | |
73 | static int loongson_i2s_pci_probe(struct pci_dev *pdev, |
74 | const struct pci_device_id *pid) |
75 | { |
76 | const struct fwnode_handle *fwnode = pdev->dev.fwnode; |
77 | struct loongson_dma_data *tx_data, *rx_data; |
78 | struct loongson_i2s *i2s; |
79 | int ret; |
80 | |
81 | if (pcim_enable_device(pdev)) { |
82 | dev_err(&pdev->dev, "pci_enable_device failed\n" ); |
83 | return -ENODEV; |
84 | } |
85 | |
86 | i2s = devm_kzalloc(dev: &pdev->dev, size: sizeof(*i2s), GFP_KERNEL); |
87 | if (!i2s) |
88 | return -ENOMEM; |
89 | |
90 | i2s->rev_id = pdev->revision; |
91 | i2s->dev = &pdev->dev; |
92 | pci_set_drvdata(pdev, data: i2s); |
93 | |
94 | ret = pcim_iomap_regions(pdev, mask: 1 << 0, name: dev_name(dev: &pdev->dev)); |
95 | if (ret < 0) { |
96 | dev_err(&pdev->dev, "iomap_regions failed\n" ); |
97 | return ret; |
98 | } |
99 | i2s->reg_base = pcim_iomap_table(pdev)[0]; |
100 | i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, |
101 | &loongson_i2s_regmap_config); |
102 | if (IS_ERR(ptr: i2s->regmap)) { |
103 | dev_err(&pdev->dev, "regmap_init_mmio failed\n" ); |
104 | return PTR_ERR(ptr: i2s->regmap); |
105 | } |
106 | |
107 | tx_data = &i2s->tx_dma_data; |
108 | rx_data = &i2s->rx_dma_data; |
109 | |
110 | tx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_TX_DATA; |
111 | tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; |
112 | |
113 | rx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_RX_DATA; |
114 | rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; |
115 | |
116 | tx_data->irq = fwnode_irq_get_byname(fwnode, name: "tx" ); |
117 | if (tx_data->irq < 0) { |
118 | dev_err(&pdev->dev, "dma tx irq invalid\n" ); |
119 | return tx_data->irq; |
120 | } |
121 | |
122 | rx_data->irq = fwnode_irq_get_byname(fwnode, name: "rx" ); |
123 | if (rx_data->irq < 0) { |
124 | dev_err(&pdev->dev, "dma rx irq invalid\n" ); |
125 | return rx_data->irq; |
126 | } |
127 | |
128 | device_property_read_u32(dev: &pdev->dev, propname: "clock-frequency" , val: &i2s->clk_rate); |
129 | if (!i2s->clk_rate) { |
130 | dev_err(&pdev->dev, "clock-frequency property invalid\n" ); |
131 | return -EINVAL; |
132 | } |
133 | |
134 | dma_set_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(64)); |
135 | |
136 | if (i2s->rev_id == 1) { |
137 | regmap_write(map: i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); |
138 | udelay(200); |
139 | } |
140 | |
141 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
142 | component_driver: &loongson_i2s_component, |
143 | dai_drv: &loongson_i2s_dai, num_dai: 1); |
144 | if (ret) { |
145 | dev_err(&pdev->dev, "register DAI failed %d\n" , ret); |
146 | return ret; |
147 | } |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static const struct pci_device_id loongson_i2s_ids[] = { |
153 | { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, |
154 | { }, |
155 | }; |
156 | MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); |
157 | |
158 | static struct pci_driver loongson_i2s_driver = { |
159 | .name = "loongson-i2s-pci" , |
160 | .id_table = loongson_i2s_ids, |
161 | .probe = loongson_i2s_pci_probe, |
162 | .driver = { |
163 | .owner = THIS_MODULE, |
164 | .pm = pm_sleep_ptr(&loongson_i2s_pm), |
165 | }, |
166 | }; |
167 | module_pci_driver(loongson_i2s_driver); |
168 | |
169 | MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver" ); |
170 | MODULE_AUTHOR("Loongson Technology Corporation Limited" ); |
171 | MODULE_LICENSE("GPL" ); |
172 | |