| 1 | /* SPDX-License-Identifier: MIT */ |
| 2 | /* |
| 3 | * Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. |
| 4 | * All Rights Reserved. |
| 5 | * |
| 6 | * Permission is hereby granted, free of charge, to any person obtaining a |
| 7 | * copy of this software and associated documentation files (the |
| 8 | * "Software"), to deal in the Software without restriction, including |
| 9 | * without limitation the rights to use, copy, modify, merge, publish, |
| 10 | * distribute, sub license, and/or sell copies of the Software, and to |
| 11 | * permit persons to whom the Software is furnished to do so, subject to |
| 12 | * the following conditions: |
| 13 | * |
| 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
| 17 | * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, |
| 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| 19 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| 20 | * USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 21 | * |
| 22 | * The above copyright notice and this permission notice (including the |
| 23 | * next paragraph) shall be included in all copies or substantial portions |
| 24 | * of the Software. |
| 25 | * |
| 26 | */ |
| 27 | |
| 28 | #include <linux/gpio/machine.h> |
| 29 | #include <linux/pm_runtime.h> |
| 30 | #include "amdgpu.h" |
| 31 | #include "isp_v4_1_1.h" |
| 32 | |
| 33 | MODULE_FIRMWARE("amdgpu/isp_4_1_1.bin" ); |
| 34 | |
| 35 | #define ISP_PERFORMANCE_STATE_LOW 0 |
| 36 | #define ISP_PERFORMANCE_STATE_HIGH 1 |
| 37 | |
| 38 | #define ISP_HIGH_PERFORMANC_XCLK 788 |
| 39 | #define ISP_HIGH_PERFORMANC_ICLK 788 |
| 40 | |
| 41 | static const unsigned int isp_4_1_1_int_srcid[MAX_ISP411_INT_SRC] = { |
| 42 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9, |
| 43 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT10, |
| 44 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT11, |
| 45 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12, |
| 46 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT13, |
| 47 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT14, |
| 48 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT15, |
| 49 | ISP_4_1__SRCID__ISP_RINGBUFFER_WPT16 |
| 50 | }; |
| 51 | |
| 52 | static struct gpiod_lookup_table isp_gpio_table = { |
| 53 | .dev_id = "amd_isp_capture" , |
| 54 | .table = { |
| 55 | GPIO_LOOKUP("AMDI0030:00" , 85, "enable_isp" , GPIO_ACTIVE_HIGH), |
| 56 | { } |
| 57 | }, |
| 58 | }; |
| 59 | |
| 60 | static struct gpiod_lookup_table isp_sensor_gpio_table = { |
| 61 | .dev_id = "i2c-ov05c10" , |
| 62 | .table = { |
| 63 | GPIO_LOOKUP("amdisp-pinctrl" , 0, "enable" , GPIO_ACTIVE_HIGH), |
| 64 | { } |
| 65 | }, |
| 66 | }; |
| 67 | |
| 68 | static int isp_poweroff(struct generic_pm_domain *genpd) |
| 69 | { |
| 70 | struct amdgpu_isp *isp = container_of(genpd, struct amdgpu_isp, ispgpd); |
| 71 | struct amdgpu_device *adev = isp->adev; |
| 72 | |
| 73 | return amdgpu_dpm_set_powergating_by_smu(adev, block_type: AMD_IP_BLOCK_TYPE_ISP, gate: true, inst: 0); |
| 74 | } |
| 75 | |
| 76 | static int isp_poweron(struct generic_pm_domain *genpd) |
| 77 | { |
| 78 | struct amdgpu_isp *isp = container_of(genpd, struct amdgpu_isp, ispgpd); |
| 79 | struct amdgpu_device *adev = isp->adev; |
| 80 | |
| 81 | return amdgpu_dpm_set_powergating_by_smu(adev, block_type: AMD_IP_BLOCK_TYPE_ISP, gate: false, inst: 0); |
| 82 | } |
| 83 | |
| 84 | static int isp_set_performance_state(struct generic_pm_domain *genpd, |
| 85 | unsigned int state) |
| 86 | { |
| 87 | struct amdgpu_isp *isp = container_of(genpd, struct amdgpu_isp, ispgpd); |
| 88 | struct amdgpu_device *adev = isp->adev; |
| 89 | u32 iclk, xclk; |
| 90 | int ret; |
| 91 | |
| 92 | switch (state) { |
| 93 | case ISP_PERFORMANCE_STATE_HIGH: |
| 94 | xclk = ISP_HIGH_PERFORMANC_XCLK; |
| 95 | iclk = ISP_HIGH_PERFORMANC_ICLK; |
| 96 | break; |
| 97 | case ISP_PERFORMANCE_STATE_LOW: |
| 98 | /* isp runs at default lowest clock-rate on power-on, do nothing */ |
| 99 | return 0; |
| 100 | default: |
| 101 | return -EINVAL; |
| 102 | } |
| 103 | |
| 104 | ret = amdgpu_dpm_set_soft_freq_range(adev, type: PP_ISPXCLK, min: xclk, max: 0); |
| 105 | if (ret) { |
| 106 | drm_err(&adev->ddev, "failed to set xclk %u to %u: %d\n" , |
| 107 | xclk, state, ret); |
| 108 | return ret; |
| 109 | } |
| 110 | |
| 111 | ret = amdgpu_dpm_set_soft_freq_range(adev, type: PP_ISPICLK, min: iclk, max: 0); |
| 112 | if (ret) { |
| 113 | drm_err(&adev->ddev, "failed to set iclk %u to %u: %d\n" , |
| 114 | iclk, state, ret); |
| 115 | return ret; |
| 116 | } |
| 117 | |
| 118 | return 0; |
| 119 | } |
| 120 | |
| 121 | static int isp_genpd_add_device(struct device *dev, void *data) |
| 122 | { |
| 123 | struct generic_pm_domain *gpd = data; |
| 124 | struct platform_device *pdev = container_of(dev, struct platform_device, dev); |
| 125 | struct amdgpu_isp *isp = container_of(gpd, struct amdgpu_isp, ispgpd); |
| 126 | struct amdgpu_device *adev = isp->adev; |
| 127 | int ret; |
| 128 | |
| 129 | if (!pdev) |
| 130 | return -EINVAL; |
| 131 | |
| 132 | if (!dev->type->name) { |
| 133 | drm_dbg(&adev->ddev, "Invalid device type to add\n" ); |
| 134 | goto exit; |
| 135 | } |
| 136 | |
| 137 | if (strcmp(dev->type->name, "mfd_device" )) { |
| 138 | drm_dbg(&adev->ddev, "Invalid isp mfd device %s to add\n" , pdev->mfd_cell->name); |
| 139 | goto exit; |
| 140 | } |
| 141 | |
| 142 | ret = pm_genpd_add_device(genpd: gpd, dev); |
| 143 | if (ret) { |
| 144 | drm_err(&adev->ddev, "Failed to add dev %s to genpd %d\n" , |
| 145 | pdev->mfd_cell->name, ret); |
| 146 | return -ENODEV; |
| 147 | } |
| 148 | |
| 149 | /* The devices will be managed by the pm ops from the parent */ |
| 150 | dev_pm_syscore_device(dev, val: true); |
| 151 | |
| 152 | exit: |
| 153 | /* Continue to add */ |
| 154 | return 0; |
| 155 | } |
| 156 | |
| 157 | static int isp_genpd_remove_device(struct device *dev, void *data) |
| 158 | { |
| 159 | struct generic_pm_domain *gpd = data; |
| 160 | struct platform_device *pdev = container_of(dev, struct platform_device, dev); |
| 161 | struct amdgpu_isp *isp = container_of(gpd, struct amdgpu_isp, ispgpd); |
| 162 | struct amdgpu_device *adev = isp->adev; |
| 163 | int ret; |
| 164 | |
| 165 | if (!pdev) |
| 166 | return -EINVAL; |
| 167 | |
| 168 | if (!dev->type->name) { |
| 169 | drm_dbg(&adev->ddev, "Invalid device type to remove\n" ); |
| 170 | goto exit; |
| 171 | } |
| 172 | |
| 173 | if (strcmp(dev->type->name, "mfd_device" )) { |
| 174 | drm_dbg(&adev->ddev, "Invalid isp mfd device %s to remove\n" , |
| 175 | pdev->mfd_cell->name); |
| 176 | goto exit; |
| 177 | } |
| 178 | |
| 179 | ret = pm_genpd_remove_device(dev); |
| 180 | if (ret) { |
| 181 | drm_err(&adev->ddev, "Failed to remove dev from genpd %d\n" , ret); |
| 182 | return -ENODEV; |
| 183 | } |
| 184 | dev_pm_syscore_device(dev, val: false); |
| 185 | |
| 186 | exit: |
| 187 | /* Continue to remove */ |
| 188 | return 0; |
| 189 | } |
| 190 | |
| 191 | static int isp_suspend_device(struct device *dev, void *data) |
| 192 | { |
| 193 | return pm_runtime_force_suspend(dev); |
| 194 | } |
| 195 | |
| 196 | static int isp_resume_device(struct device *dev, void *data) |
| 197 | { |
| 198 | return pm_runtime_force_resume(dev); |
| 199 | } |
| 200 | |
| 201 | static int isp_v4_1_1_hw_suspend(struct amdgpu_isp *isp) |
| 202 | { |
| 203 | int r; |
| 204 | |
| 205 | r = device_for_each_child(parent: isp->parent, NULL, |
| 206 | fn: isp_suspend_device); |
| 207 | if (r) |
| 208 | dev_err(isp->parent, "failed to suspend hw devices (%d)\n" , r); |
| 209 | |
| 210 | return r; |
| 211 | } |
| 212 | |
| 213 | static int isp_v4_1_1_hw_resume(struct amdgpu_isp *isp) |
| 214 | { |
| 215 | int r; |
| 216 | |
| 217 | r = device_for_each_child(parent: isp->parent, NULL, |
| 218 | fn: isp_resume_device); |
| 219 | if (r) |
| 220 | dev_err(isp->parent, "failed to resume hw device (%d)\n" , r); |
| 221 | |
| 222 | return r; |
| 223 | } |
| 224 | |
| 225 | static int isp_v4_1_1_hw_init(struct amdgpu_isp *isp) |
| 226 | { |
| 227 | const struct software_node *amd_camera_node, *isp4_node; |
| 228 | struct amdgpu_device *adev = isp->adev; |
| 229 | struct acpi_device *acpi_dev; |
| 230 | int idx, int_idx, num_res, r; |
| 231 | u64 isp_base; |
| 232 | |
| 233 | if (adev->rmmio_size == 0 || adev->rmmio_size < 0x5289) |
| 234 | return -EINVAL; |
| 235 | |
| 236 | r = amdgpu_acpi_get_isp4_dev(dev: &acpi_dev); |
| 237 | if (r) { |
| 238 | drm_dbg(&adev->ddev, "Invalid isp platform detected (%d)" , r); |
| 239 | /* allow GPU init to progress */ |
| 240 | return 0; |
| 241 | } |
| 242 | |
| 243 | /* add GPIO resources required for OMNI5C10 sensor */ |
| 244 | if (!strcmp("OMNI5C10" , acpi_device_hid(device: acpi_dev))) { |
| 245 | gpiod_add_lookup_table(table: &isp_gpio_table); |
| 246 | gpiod_add_lookup_table(table: &isp_sensor_gpio_table); |
| 247 | } |
| 248 | |
| 249 | isp_base = adev->rmmio_base; |
| 250 | |
| 251 | isp->ispgpd.name = "ISP_v_4_1_1" ; |
| 252 | isp->ispgpd.power_off = isp_poweroff; |
| 253 | isp->ispgpd.power_on = isp_poweron; |
| 254 | isp->ispgpd.set_performance_state = isp_set_performance_state; |
| 255 | |
| 256 | r = pm_genpd_init(genpd: &isp->ispgpd, NULL, is_off: true); |
| 257 | if (r) { |
| 258 | drm_err(&adev->ddev, "failed to initialize genpd (%d)\n" , r); |
| 259 | return -EINVAL; |
| 260 | } |
| 261 | |
| 262 | isp->isp_cell = kcalloc(3, sizeof(struct mfd_cell), GFP_KERNEL); |
| 263 | if (!isp->isp_cell) { |
| 264 | r = -ENOMEM; |
| 265 | drm_err(&adev->ddev, "isp mfd cell alloc failed (%d)\n" , r); |
| 266 | goto failure; |
| 267 | } |
| 268 | |
| 269 | num_res = MAX_ISP411_MEM_RES + MAX_ISP411_INT_SRC; |
| 270 | |
| 271 | isp->isp_res = kcalloc(num_res, sizeof(struct resource), |
| 272 | GFP_KERNEL); |
| 273 | if (!isp->isp_res) { |
| 274 | r = -ENOMEM; |
| 275 | drm_err(&adev->ddev, "isp mfd resource alloc failed (%d)\n" , r); |
| 276 | goto failure; |
| 277 | } |
| 278 | |
| 279 | isp->isp_pdata = kzalloc(sizeof(*isp->isp_pdata), GFP_KERNEL); |
| 280 | if (!isp->isp_pdata) { |
| 281 | r = -ENOMEM; |
| 282 | drm_err(&adev->ddev, "isp platform data alloc failed (%d)\n" , r); |
| 283 | goto failure; |
| 284 | } |
| 285 | |
| 286 | amd_camera_node = (const struct software_node *)acpi_dev->driver_data; |
| 287 | isp4_node = software_node_find_by_name(parent: amd_camera_node, name: "isp4" ); |
| 288 | |
| 289 | /* initialize isp platform data */ |
| 290 | isp->isp_pdata->adev = (void *)adev; |
| 291 | isp->isp_pdata->asic_type = adev->asic_type; |
| 292 | isp->isp_pdata->base_rmmio_size = adev->rmmio_size; |
| 293 | |
| 294 | isp->isp_res[0].name = "isp_4_1_1_reg" ; |
| 295 | isp->isp_res[0].flags = IORESOURCE_MEM; |
| 296 | isp->isp_res[0].start = isp_base; |
| 297 | isp->isp_res[0].end = isp_base + ISP_REGS_OFFSET_END; |
| 298 | |
| 299 | isp->isp_res[1].name = "isp_4_1_1_phy0_reg" ; |
| 300 | isp->isp_res[1].flags = IORESOURCE_MEM; |
| 301 | isp->isp_res[1].start = isp_base + ISP411_PHY0_OFFSET; |
| 302 | isp->isp_res[1].end = isp_base + ISP411_PHY0_OFFSET + ISP411_PHY0_SIZE; |
| 303 | |
| 304 | for (idx = MAX_ISP411_MEM_RES, int_idx = 0; idx < num_res; idx++, int_idx++) { |
| 305 | isp->isp_res[idx].name = "isp_4_1_1_irq" ; |
| 306 | isp->isp_res[idx].flags = IORESOURCE_IRQ; |
| 307 | isp->isp_res[idx].start = |
| 308 | amdgpu_irq_create_mapping(adev, src_id: isp_4_1_1_int_srcid[int_idx]); |
| 309 | isp->isp_res[idx].end = |
| 310 | isp->isp_res[idx].start; |
| 311 | } |
| 312 | |
| 313 | isp->isp_cell[0].name = "amd_isp_capture" ; |
| 314 | isp->isp_cell[0].num_resources = num_res; |
| 315 | isp->isp_cell[0].resources = &isp->isp_res[0]; |
| 316 | isp->isp_cell[0].platform_data = isp->isp_pdata; |
| 317 | isp->isp_cell[0].swnode = isp4_node; |
| 318 | isp->isp_cell[0].pdata_size = sizeof(struct isp_platform_data); |
| 319 | |
| 320 | /* initialize isp i2c platform data */ |
| 321 | isp->isp_i2c_res = kcalloc(1, sizeof(struct resource), GFP_KERNEL); |
| 322 | if (!isp->isp_i2c_res) { |
| 323 | r = -ENOMEM; |
| 324 | drm_err(&adev->ddev, "isp mfd res alloc failed (%d)\n" , r); |
| 325 | goto failure; |
| 326 | } |
| 327 | |
| 328 | isp->isp_i2c_res[0].name = "isp_i2c0_reg" ; |
| 329 | isp->isp_i2c_res[0].flags = IORESOURCE_MEM; |
| 330 | isp->isp_i2c_res[0].start = isp_base + ISP411_I2C0_OFFSET; |
| 331 | isp->isp_i2c_res[0].end = isp_base + ISP411_I2C0_OFFSET + ISP411_I2C0_SIZE; |
| 332 | |
| 333 | isp->isp_cell[1].name = "amd_isp_i2c_designware" ; |
| 334 | isp->isp_cell[1].num_resources = 1; |
| 335 | isp->isp_cell[1].resources = &isp->isp_i2c_res[0]; |
| 336 | isp->isp_cell[1].platform_data = isp->isp_pdata; |
| 337 | isp->isp_cell[1].pdata_size = sizeof(struct isp_platform_data); |
| 338 | |
| 339 | /* initialize isp gpiochip platform data */ |
| 340 | isp->isp_gpio_res = kcalloc(1, sizeof(struct resource), GFP_KERNEL); |
| 341 | if (!isp->isp_gpio_res) { |
| 342 | r = -ENOMEM; |
| 343 | drm_err(&adev->ddev, "isp gpio resource alloc failed (%d)\n" , r); |
| 344 | goto failure; |
| 345 | } |
| 346 | |
| 347 | isp->isp_gpio_res[0].name = "isp_gpio_reg" ; |
| 348 | isp->isp_gpio_res[0].flags = IORESOURCE_MEM; |
| 349 | isp->isp_gpio_res[0].start = isp_base + ISP411_GPIO_SENSOR_OFFSET; |
| 350 | isp->isp_gpio_res[0].end = isp_base + ISP411_GPIO_SENSOR_OFFSET + |
| 351 | ISP411_GPIO_SENSOR_SIZE; |
| 352 | |
| 353 | isp->isp_cell[2].name = "amdisp-pinctrl" ; |
| 354 | isp->isp_cell[2].num_resources = 1; |
| 355 | isp->isp_cell[2].resources = &isp->isp_gpio_res[0]; |
| 356 | isp->isp_cell[2].platform_data = isp->isp_pdata; |
| 357 | isp->isp_cell[2].pdata_size = sizeof(struct isp_platform_data); |
| 358 | |
| 359 | /* add only amd_isp_capture and amd_isp_i2c_designware to genpd */ |
| 360 | r = mfd_add_hotplug_devices(parent: isp->parent, cells: isp->isp_cell, n_devs: 2); |
| 361 | if (r) { |
| 362 | drm_err(&adev->ddev, "add mfd hotplug device failed (%d)\n" , r); |
| 363 | goto failure; |
| 364 | } |
| 365 | |
| 366 | r = device_for_each_child(parent: isp->parent, data: &isp->ispgpd, |
| 367 | fn: isp_genpd_add_device); |
| 368 | if (r) { |
| 369 | drm_err(&adev->ddev, "failed to add devices to genpd (%d)\n" , r); |
| 370 | goto failure; |
| 371 | } |
| 372 | |
| 373 | r = mfd_add_hotplug_devices(parent: isp->parent, cells: &isp->isp_cell[2], n_devs: 1); |
| 374 | if (r) { |
| 375 | drm_err(&adev->ddev, "add pinctl hotplug device failed (%d)\n" , r); |
| 376 | goto failure; |
| 377 | } |
| 378 | |
| 379 | return 0; |
| 380 | |
| 381 | failure: |
| 382 | |
| 383 | kfree(objp: isp->isp_pdata); |
| 384 | kfree(objp: isp->isp_res); |
| 385 | kfree(objp: isp->isp_cell); |
| 386 | kfree(objp: isp->isp_i2c_res); |
| 387 | kfree(objp: isp->isp_gpio_res); |
| 388 | |
| 389 | return r; |
| 390 | } |
| 391 | |
| 392 | static int isp_v4_1_1_hw_fini(struct amdgpu_isp *isp) |
| 393 | { |
| 394 | device_for_each_child(parent: isp->parent, NULL, |
| 395 | fn: isp_genpd_remove_device); |
| 396 | |
| 397 | mfd_remove_devices(parent: isp->parent); |
| 398 | |
| 399 | kfree(objp: isp->isp_res); |
| 400 | kfree(objp: isp->isp_cell); |
| 401 | kfree(objp: isp->isp_pdata); |
| 402 | kfree(objp: isp->isp_i2c_res); |
| 403 | kfree(objp: isp->isp_gpio_res); |
| 404 | |
| 405 | return 0; |
| 406 | } |
| 407 | |
| 408 | static const struct isp_funcs isp_v4_1_1_funcs = { |
| 409 | .hw_init = isp_v4_1_1_hw_init, |
| 410 | .hw_fini = isp_v4_1_1_hw_fini, |
| 411 | .hw_suspend = isp_v4_1_1_hw_suspend, |
| 412 | .hw_resume = isp_v4_1_1_hw_resume, |
| 413 | }; |
| 414 | |
| 415 | void isp_v4_1_1_set_isp_funcs(struct amdgpu_isp *isp) |
| 416 | { |
| 417 | isp->funcs = &isp_v4_1_1_funcs; |
| 418 | } |
| 419 | |